nf_conntrack_sip.c 15.6 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
static int digits_len(const struct nf_conn *, const char *, const char *, int *);
static int epaddr_len(const struct nf_conn *, const char *, const char *, int *);
static int skp_digits_len(const struct nf_conn *, const char *, const char *, int *);
static int skp_epaddr_len(const struct nf_conn *, const char *, const char *, int *);
54 55 56 57 58 59 60 61 62

struct sip_header_nfo {
	const char	*lname;
	const char	*sname;
	const char	*ln_str;
	size_t		lnlen;
	size_t		snlen;
	size_t		ln_strlen;
	int		case_sensitive;
63
	int		(*match_len)(const struct nf_conn *, const char *,
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
				     const char *, int *);
};

static const struct sip_header_nfo ct_sip_hdrs[] = {
	[POS_FROM] = {		/* SIP From header */
		.lname		= "From:",
		.lnlen		= sizeof("From:") - 1,
		.sname		= "\r\nf:",
		.snlen		= sizeof("\r\nf:") - 1,
		.ln_str		= "sip:",
		.ln_strlen	= sizeof("sip:") - 1,
		.match_len	= skp_epaddr_len,
	},
	[POS_TO] = {		/* SIP To header */
		.lname		= "To:",
		.lnlen		= sizeof("To:") - 1,
		.sname		= "\r\nt:",
		.snlen		= sizeof("\r\nt:") - 1,
		.ln_str		= "sip:",
		.ln_strlen	= sizeof("sip:") - 1,
		.match_len	= skp_epaddr_len
	},
	[POS_VIA] = { 		/* SIP Via header */
		.lname		= "Via:",
		.lnlen		= sizeof("Via:") - 1,
		.sname		= "\r\nv:",
		.snlen		= sizeof("\r\nv:") - 1, /* rfc3261 "\r\n" */
		.ln_str		= "UDP ",
		.ln_strlen	= sizeof("UDP ") - 1,
		.match_len	= epaddr_len,
	},
	[POS_CONTACT] = { 	/* SIP Contact header */
		.lname		= "Contact:",
		.lnlen		= sizeof("Contact:") - 1,
		.sname		= "\r\nm:",
		.snlen		= sizeof("\r\nm:") - 1,
		.ln_str		= "sip:",
		.ln_strlen	= sizeof("sip:") - 1,
		.match_len	= skp_epaddr_len
	},
	[POS_CONTENT] = { 	/* SIP Content length header */
		.lname		= "Content-Length:",
		.lnlen		= sizeof("Content-Length:") - 1,
		.sname		= "\r\nl:",
		.snlen		= sizeof("\r\nl:") - 1,
		.ln_str		= ":",
		.ln_strlen	= sizeof(":") - 1,
		.match_len	= skp_digits_len
	},
};

J
Joe Perches 已提交
115
/* get line length until first CR or LF seen. */
116 117 118 119
int ct_sip_lnlen(const char *line, const char *limit)
{
	const char *k = line;

120
	while ((line < limit) && (*line == '\r' || *line == '\n'))
121 122
		line++;

123
	while (line < limit) {
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
		if (*line == '\r' || *line == '\n')
			break;
		line++;
	}
	return line - k;
}
EXPORT_SYMBOL_GPL(ct_sip_lnlen);

/* Linear string search, case sensitive. */
const char *ct_sip_search(const char *needle, const char *haystack,
			  size_t needle_len, size_t haystack_len,
			  int case_sensitive)
{
	const char *limit = haystack + (haystack_len - needle_len);

139
	while (haystack < limit) {
140 141 142 143 144 145 146 147 148 149 150 151 152
		if (case_sensitive) {
			if (strncmp(haystack, needle, needle_len) == 0)
				return haystack;
		} else {
			if (strnicmp(haystack, needle, needle_len) == 0)
				return haystack;
		}
		haystack++;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(ct_sip_search);

153 154 155 156 157 158 159 160 161 162 163 164
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;
}

165
static int digits_len(const struct nf_conn *ct, const char *dptr,
166 167 168
		      const char *limit, int *shift)
{
	int len = 0;
169
	while (dptr < limit && isdigit(*dptr)) {
170 171 172 173 174 175
		dptr++;
		len++;
	}
	return len;
}

J
Joe Perches 已提交
176
/* get digits length, skipping blank spaces. */
177
static int skp_digits_len(const struct nf_conn *ct, const char *dptr,
178 179
			  const char *limit, int *shift)
{
180
	for (; dptr < limit && *dptr == ' '; dptr++)
181 182 183 184 185
		(*shift)++;

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

186 187 188
static int parse_addr(const struct nf_conn *ct, const char *cp,
                      const char **endp, union nf_inet_addr *addr,
                      const char *limit)
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
{
	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. */
213
static int epaddr_len(const struct nf_conn *ct, const char *dptr,
214 215
		      const char *limit, int *shift)
{
216
	union nf_inet_addr addr;
217 218 219
	const char *aux = dptr;

	if (!parse_addr(ct, dptr, &dptr, &addr, limit)) {
220
		pr_debug("ip: %s parse failed.!\n", dptr);
221 222 223 224 225 226 227 228 229 230 231 232
		return 0;
	}

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

/* get address length, skiping user info. */
233
static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr,
234 235
			  const char *limit, int *shift)
{
236
	const char *start = dptr;
237 238
	int s = *shift;

239 240 241
	/* 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. */
242
	while (dptr < limit &&
243
	       *dptr != '@' && *dptr != '\r' && *dptr != '\n') {
244
		(*shift)++;
245 246
		dptr++;
	}
247

248
	if (dptr < limit && *dptr == '@') {
249 250
		dptr++;
		(*shift)++;
251 252
	} else {
		dptr = start;
253
		*shift = s;
254
	}
255 256 257 258

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

259 260 261 262 263 264 265 266 267 268 269 270 271 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
/* 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,
			 unsigned int *matchoff, unsigned int *matchlen)
{
	const char *start = dptr, *limit = dptr + datalen;
	unsigned int mlen;
	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;
	}
	*matchlen = skp_epaddr_len(ct, dptr, limit, &shift);
	if (!*matchlen)
		return 0;
	*matchoff = dptr - start + shift;
	return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_request);

297
/* Returns 0 if not found, -1 error parsing. */
298
int ct_sip_get_info(const struct nf_conn *ct,
299 300 301 302 303 304 305 306 307 308 309
		    const char *dptr, size_t dlen,
		    unsigned int *matchoff,
		    unsigned int *matchlen,
		    enum sip_header_pos pos)
{
	const struct sip_header_nfo *hnfo = &ct_sip_hdrs[pos];
	const char *limit, *aux, *k = dptr;
	int shift = 0;

	limit = dptr + (dlen - hnfo->lnlen);

310
	while (dptr < limit) {
311
		if ((strncmp(dptr, hnfo->lname, hnfo->lnlen) != 0) &&
312 313
		    (hnfo->sname == NULL ||
		     strncmp(dptr, hnfo->sname, hnfo->snlen) != 0)) {
314 315 316 317
			dptr++;
			continue;
		}
		aux = ct_sip_search(hnfo->ln_str, dptr, hnfo->ln_strlen,
318
				    ct_sip_lnlen(dptr, limit),
319 320
				    hnfo->case_sensitive);
		if (!aux) {
321 322
			pr_debug("'%s' not found in '%s'.\n", hnfo->ln_str,
				 hnfo->lname);
323 324 325 326 327 328 329 330 331 332
			return -1;
		}
		aux += hnfo->ln_strlen;

		*matchlen = hnfo->match_len(ct, aux, limit, &shift);
		if (!*matchlen)
			return -1;

		*matchoff = (aux - k) + shift;

333 334
		pr_debug("%s match succeeded! - len: %u\n", hnfo->lname,
			 *matchlen);
335 336
		return 1;
	}
337
	pr_debug("%s header not found.\n", hnfo->lname);
338 339 340 341
	return 0;
}
EXPORT_SYMBOL_GPL(ct_sip_get_info);

342 343 344 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
/* 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);

428
static int set_expected_rtp(struct sk_buff *skb,
429 430
			    const char **dptr, unsigned int *datalen,
			    union nf_inet_addr *addr, __be16 port)
431 432
{
	struct nf_conntrack_expect *exp;
433 434
	enum ip_conntrack_info ctinfo;
	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
435 436 437 438 439
	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;

440
	exp = nf_ct_expect_alloc(ct);
441 442
	if (exp == NULL)
		return NF_DROP;
443
	nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, family,
444 445
			  &ct->tuplehash[!dir].tuple.src.u3, addr,
			  IPPROTO_UDP, NULL, &port);
446 447 448

	nf_nat_sdp = rcu_dereference(nf_nat_sdp_hook);
	if (nf_nat_sdp && ct->status & IPS_NAT_MASK)
449
		ret = nf_nat_sdp(skb, dptr, datalen, exp);
450
	else {
451
		if (nf_ct_expect_related(exp) != 0)
452 453 454 455
			ret = NF_DROP;
		else
			ret = NF_ACCEPT;
	}
456
	nf_ct_expect_put(exp);
457 458 459 460

	return ret;
}

461
static int sip_help(struct sk_buff *skb,
462 463 464 465 466
		    unsigned int protoff,
		    struct nf_conn *ct,
		    enum ip_conntrack_info ctinfo)
{
	int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
467
	union nf_inet_addr addr;
468 469 470
	unsigned int dataoff, datalen;
	const char *dptr;
	int ret = NF_ACCEPT;
471
	unsigned int matchoff, matchlen;
472
	u_int16_t port;
473
	enum sdp_header_types type;
474 475 476 477
	typeof(nf_nat_sip_hook) nf_nat_sip;

	/* No Data ? */
	dataoff = protoff + sizeof(struct udphdr);
478
	if (dataoff >= skb->len)
479 480
		return NF_ACCEPT;

481
	nf_ct_refresh(ct, skb, sip_timeout * HZ);
482

483 484
	if (!skb_is_nonlinear(skb))
		dptr = skb->data + dataoff;
485
	else {
486
		pr_debug("Copy of skbuff not supported yet.\n");
487 488 489 490 491
		goto out;
	}

	nf_nat_sip = rcu_dereference(nf_nat_sip_hook);
	if (nf_nat_sip && ct->status & IPS_NAT_MASK) {
492
		if (!nf_nat_sip(skb, &dptr, &datalen)) {
493 494 495 496 497
			ret = NF_DROP;
			goto out;
		}
	}

498
	datalen = skb->len - dataoff;
499
	if (datalen < strlen("SIP/2.0 200"))
500 501 502
		goto out;

	/* RTP info only in some SDP pkts */
503 504 505 506 507
	if (strnicmp(dptr, "INVITE", strlen("INVITE")) != 0 &&
	    strnicmp(dptr, "UPDATE", strlen("UPDATE")) != 0 &&
	    strnicmp(dptr, "SIP/2.0 180", strlen("SIP/2.0 180")) != 0 &&
	    strnicmp(dptr, "SIP/2.0 183", strlen("SIP/2.0 183")) != 0 &&
	    strnicmp(dptr, "SIP/2.0 200", strlen("SIP/2.0 200")) != 0) {
508 509 510
		goto out;
	}
	/* Get address and port from SDP packet. */
511 512 513 514
	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) {
515 516 517

		/* We'll drop only if there are parse problems. */
		if (!parse_addr(ct, dptr + matchoff, NULL, &addr,
518
				dptr + datalen)) {
519 520 521
			ret = NF_DROP;
			goto out;
		}
522 523 524
		if (ct_sip_get_sdp_header(ct, dptr, 0, datalen,
					  SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
					  &matchoff, &matchlen) > 0) {
525 526 527 528 529 530

			port = simple_strtoul(dptr + matchoff, NULL, 10);
			if (port < 1024) {
				ret = NF_DROP;
				goto out;
			}
531 532
			ret = set_expected_rtp(skb, &dptr, &datalen,
					       &addr, htons(port));
533 534 535 536 537 538 539 540 541
		}
	}
out:
	return ret;
}

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

542 543 544 545 546
static const struct nf_conntrack_expect_policy sip_exp_policy = {
	.max_expected	= 2,
	.timeout	= 3 * 60,
};

547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
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]);
576
			sip[i][j].expect_policy = &sip_exp_policy;
577 578 579 580 581 582 583 584 585 586
			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;

587
			pr_debug("port #%u: %u\n", i, ports[i]);
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603

			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);