19fafcd7bSPatrick McHardy /* SIP extension for IP connection tracking.
29fafcd7bSPatrick McHardy  *
39fafcd7bSPatrick McHardy  * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
49fafcd7bSPatrick McHardy  * based on RR's ip_conntrack_ftp.c and other modules.
59fafcd7bSPatrick McHardy  *
69fafcd7bSPatrick McHardy  * This program is free software; you can redistribute it and/or modify
79fafcd7bSPatrick McHardy  * it under the terms of the GNU General Public License version 2 as
89fafcd7bSPatrick McHardy  * published by the Free Software Foundation.
99fafcd7bSPatrick McHardy  */
109fafcd7bSPatrick McHardy 
119fafcd7bSPatrick McHardy #include <linux/module.h>
129fafcd7bSPatrick McHardy #include <linux/ctype.h>
139fafcd7bSPatrick McHardy #include <linux/skbuff.h>
149fafcd7bSPatrick McHardy #include <linux/inet.h>
159fafcd7bSPatrick McHardy #include <linux/in.h>
169fafcd7bSPatrick McHardy #include <linux/udp.h>
171863f096SYasuyuki Kozakai #include <linux/netfilter.h>
189fafcd7bSPatrick McHardy 
199fafcd7bSPatrick McHardy #include <net/netfilter/nf_conntrack.h>
209fafcd7bSPatrick McHardy #include <net/netfilter/nf_conntrack_expect.h>
219fafcd7bSPatrick McHardy #include <net/netfilter/nf_conntrack_helper.h>
229fafcd7bSPatrick McHardy #include <linux/netfilter/nf_conntrack_sip.h>
239fafcd7bSPatrick McHardy 
249fafcd7bSPatrick McHardy MODULE_LICENSE("GPL");
259fafcd7bSPatrick McHardy MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
269fafcd7bSPatrick McHardy MODULE_DESCRIPTION("SIP connection tracking helper");
279fafcd7bSPatrick McHardy MODULE_ALIAS("ip_conntrack_sip");
289fafcd7bSPatrick McHardy 
299fafcd7bSPatrick McHardy #define MAX_PORTS	8
309fafcd7bSPatrick McHardy static unsigned short ports[MAX_PORTS];
312f0d2f10SStephen Hemminger static unsigned int ports_c;
329fafcd7bSPatrick McHardy module_param_array(ports, ushort, &ports_c, 0400);
339fafcd7bSPatrick McHardy MODULE_PARM_DESC(ports, "port numbers of SIP servers");
349fafcd7bSPatrick McHardy 
359fafcd7bSPatrick McHardy static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT;
369fafcd7bSPatrick McHardy module_param(sip_timeout, uint, 0600);
379fafcd7bSPatrick McHardy MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session");
389fafcd7bSPatrick McHardy 
393db05feaSHerbert Xu unsigned int (*nf_nat_sip_hook)(struct sk_buff *skb,
402a6cfb22SPatrick McHardy 				const char **dptr,
412a6cfb22SPatrick McHardy 				unsigned int *datalen) __read_mostly;
429fafcd7bSPatrick McHardy EXPORT_SYMBOL_GPL(nf_nat_sip_hook);
439fafcd7bSPatrick McHardy 
443db05feaSHerbert Xu unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb,
452a6cfb22SPatrick McHardy 				const char **dptr,
46212440a7SPatrick McHardy 				unsigned int *datalen,
47212440a7SPatrick McHardy 				struct nf_conntrack_expect *exp) __read_mostly;
489fafcd7bSPatrick McHardy EXPORT_SYMBOL_GPL(nf_nat_sdp_hook);
499fafcd7bSPatrick McHardy 
50ac367740SPatrick McHardy static int string_len(const struct nf_conn *ct, const char *dptr,
51ac367740SPatrick McHardy 		      const char *limit, int *shift)
52ac367740SPatrick McHardy {
53ac367740SPatrick McHardy 	int len = 0;
54ac367740SPatrick McHardy 
55ac367740SPatrick McHardy 	while (dptr < limit && isalpha(*dptr)) {
56ac367740SPatrick McHardy 		dptr++;
57ac367740SPatrick McHardy 		len++;
58ac367740SPatrick McHardy 	}
59ac367740SPatrick McHardy 	return len;
60ac367740SPatrick McHardy }
61ac367740SPatrick McHardy 
6213f7d63cSJan Engelhardt static int digits_len(const struct nf_conn *ct, const char *dptr,
639fafcd7bSPatrick McHardy 		      const char *limit, int *shift)
649fafcd7bSPatrick McHardy {
659fafcd7bSPatrick McHardy 	int len = 0;
66b1ec488bSPatrick McHardy 	while (dptr < limit && isdigit(*dptr)) {
679fafcd7bSPatrick McHardy 		dptr++;
689fafcd7bSPatrick McHardy 		len++;
699fafcd7bSPatrick McHardy 	}
709fafcd7bSPatrick McHardy 	return len;
719fafcd7bSPatrick McHardy }
729fafcd7bSPatrick McHardy 
7313f7d63cSJan Engelhardt static int parse_addr(const struct nf_conn *ct, const char *cp,
7413f7d63cSJan Engelhardt                       const char **endp, union nf_inet_addr *addr,
7513f7d63cSJan Engelhardt                       const char *limit)
769fafcd7bSPatrick McHardy {
779fafcd7bSPatrick McHardy 	const char *end;
789fafcd7bSPatrick McHardy 	int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
799fafcd7bSPatrick McHardy 	int ret = 0;
809fafcd7bSPatrick McHardy 
819fafcd7bSPatrick McHardy 	switch (family) {
829fafcd7bSPatrick McHardy 	case AF_INET:
839fafcd7bSPatrick McHardy 		ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
849fafcd7bSPatrick McHardy 		break;
859fafcd7bSPatrick McHardy 	case AF_INET6:
869fafcd7bSPatrick McHardy 		ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
879fafcd7bSPatrick McHardy 		break;
889fafcd7bSPatrick McHardy 	default:
899fafcd7bSPatrick McHardy 		BUG();
909fafcd7bSPatrick McHardy 	}
919fafcd7bSPatrick McHardy 
929fafcd7bSPatrick McHardy 	if (ret == 0 || end == cp)
939fafcd7bSPatrick McHardy 		return 0;
949fafcd7bSPatrick McHardy 	if (endp)
959fafcd7bSPatrick McHardy 		*endp = end;
969fafcd7bSPatrick McHardy 	return 1;
979fafcd7bSPatrick McHardy }
989fafcd7bSPatrick McHardy 
999fafcd7bSPatrick McHardy /* skip ip address. returns its length. */
10013f7d63cSJan Engelhardt static int epaddr_len(const struct nf_conn *ct, const char *dptr,
1019fafcd7bSPatrick McHardy 		      const char *limit, int *shift)
1029fafcd7bSPatrick McHardy {
103643a2c15SJan Engelhardt 	union nf_inet_addr addr;
1049fafcd7bSPatrick McHardy 	const char *aux = dptr;
1059fafcd7bSPatrick McHardy 
1069fafcd7bSPatrick McHardy 	if (!parse_addr(ct, dptr, &dptr, &addr, limit)) {
1070d53778eSPatrick McHardy 		pr_debug("ip: %s parse failed.!\n", dptr);
1089fafcd7bSPatrick McHardy 		return 0;
1099fafcd7bSPatrick McHardy 	}
1109fafcd7bSPatrick McHardy 
1119fafcd7bSPatrick McHardy 	/* Port number */
1129fafcd7bSPatrick McHardy 	if (*dptr == ':') {
1139fafcd7bSPatrick McHardy 		dptr++;
1149fafcd7bSPatrick McHardy 		dptr += digits_len(ct, dptr, limit, shift);
1159fafcd7bSPatrick McHardy 	}
1169fafcd7bSPatrick McHardy 	return dptr - aux;
1179fafcd7bSPatrick McHardy }
1189fafcd7bSPatrick McHardy 
1199fafcd7bSPatrick McHardy /* get address length, skiping user info. */
12013f7d63cSJan Engelhardt static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr,
1219fafcd7bSPatrick McHardy 			  const char *limit, int *shift)
1229fafcd7bSPatrick McHardy {
123aa584edaSPatrick McHardy 	const char *start = dptr;
1249fafcd7bSPatrick McHardy 	int s = *shift;
1259fafcd7bSPatrick McHardy 
1267da5bfbbSLars Immisch 	/* Search for @, but stop at the end of the line.
1277da5bfbbSLars Immisch 	 * We are inside a sip: URI, so we don't need to worry about
1287da5bfbbSLars Immisch 	 * continuation lines. */
129b1ec488bSPatrick McHardy 	while (dptr < limit &&
1307da5bfbbSLars Immisch 	       *dptr != '@' && *dptr != '\r' && *dptr != '\n') {
1319fafcd7bSPatrick McHardy 		(*shift)++;
1327da5bfbbSLars Immisch 		dptr++;
1337da5bfbbSLars Immisch 	}
1349fafcd7bSPatrick McHardy 
135b1ec488bSPatrick McHardy 	if (dptr < limit && *dptr == '@') {
1369fafcd7bSPatrick McHardy 		dptr++;
1379fafcd7bSPatrick McHardy 		(*shift)++;
138aa584edaSPatrick McHardy 	} else {
139aa584edaSPatrick McHardy 		dptr = start;
1409fafcd7bSPatrick McHardy 		*shift = s;
141aa584edaSPatrick McHardy 	}
1429fafcd7bSPatrick McHardy 
1439fafcd7bSPatrick McHardy 	return epaddr_len(ct, dptr, limit, shift);
1449fafcd7bSPatrick McHardy }
1459fafcd7bSPatrick McHardy 
146ac367740SPatrick McHardy /* Parse a SIP request line of the form:
147ac367740SPatrick McHardy  *
148ac367740SPatrick McHardy  * Request-Line = Method SP Request-URI SP SIP-Version CRLF
149ac367740SPatrick McHardy  *
150ac367740SPatrick McHardy  * and return the offset and length of the address contained in the Request-URI.
151ac367740SPatrick McHardy  */
152ac367740SPatrick McHardy int ct_sip_parse_request(const struct nf_conn *ct,
153ac367740SPatrick McHardy 			 const char *dptr, unsigned int datalen,
154624f8b7bSPatrick McHardy 			 unsigned int *matchoff, unsigned int *matchlen,
155624f8b7bSPatrick McHardy 			 union nf_inet_addr *addr, __be16 *port)
156ac367740SPatrick McHardy {
157624f8b7bSPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen, *end;
158ac367740SPatrick McHardy 	unsigned int mlen;
159624f8b7bSPatrick McHardy 	unsigned int p;
160ac367740SPatrick McHardy 	int shift = 0;
161ac367740SPatrick McHardy 
162ac367740SPatrick McHardy 	/* Skip method and following whitespace */
163ac367740SPatrick McHardy 	mlen = string_len(ct, dptr, limit, NULL);
164ac367740SPatrick McHardy 	if (!mlen)
165ac367740SPatrick McHardy 		return 0;
166ac367740SPatrick McHardy 	dptr += mlen;
167ac367740SPatrick McHardy 	if (++dptr >= limit)
168ac367740SPatrick McHardy 		return 0;
169ac367740SPatrick McHardy 
170ac367740SPatrick McHardy 	/* Find SIP URI */
171ac367740SPatrick McHardy 	limit -= strlen("sip:");
172ac367740SPatrick McHardy 	for (; dptr < limit; dptr++) {
173ac367740SPatrick McHardy 		if (*dptr == '\r' || *dptr == '\n')
174ac367740SPatrick McHardy 			return -1;
175ac367740SPatrick McHardy 		if (strnicmp(dptr, "sip:", strlen("sip:")) == 0)
176ac367740SPatrick McHardy 			break;
177ac367740SPatrick McHardy 	}
178624f8b7bSPatrick McHardy 	if (!skp_epaddr_len(ct, dptr, limit, &shift))
179ac367740SPatrick McHardy 		return 0;
180624f8b7bSPatrick McHardy 	dptr += shift;
181624f8b7bSPatrick McHardy 
182624f8b7bSPatrick McHardy 	if (!parse_addr(ct, dptr, &end, addr, limit))
183624f8b7bSPatrick McHardy 		return -1;
184624f8b7bSPatrick McHardy 	if (end < limit && *end == ':') {
185624f8b7bSPatrick McHardy 		end++;
186624f8b7bSPatrick McHardy 		p = simple_strtoul(end, (char **)&end, 10);
187624f8b7bSPatrick McHardy 		if (p < 1024 || p > 65535)
188624f8b7bSPatrick McHardy 			return -1;
189624f8b7bSPatrick McHardy 		*port = htons(p);
190624f8b7bSPatrick McHardy 	} else
191624f8b7bSPatrick McHardy 		*port = htons(SIP_PORT);
192624f8b7bSPatrick McHardy 
193624f8b7bSPatrick McHardy 	if (end == dptr)
194624f8b7bSPatrick McHardy 		return 0;
195624f8b7bSPatrick McHardy 	*matchoff = dptr - start;
196624f8b7bSPatrick McHardy 	*matchlen = end - dptr;
197ac367740SPatrick McHardy 	return 1;
198ac367740SPatrick McHardy }
199ac367740SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_parse_request);
200ac367740SPatrick McHardy 
201ea45f12aSPatrick McHardy /* SIP header parsing: SIP headers are located at the beginning of a line, but
202ea45f12aSPatrick McHardy  * may span several lines, in which case the continuation lines begin with a
203ea45f12aSPatrick McHardy  * whitespace character. RFC 2543 allows lines to be terminated with CR, LF or
204ea45f12aSPatrick McHardy  * CRLF, RFC 3261 allows only CRLF, we support both.
205ea45f12aSPatrick McHardy  *
206ea45f12aSPatrick McHardy  * Headers are followed by (optionally) whitespace, a colon, again (optionally)
207ea45f12aSPatrick McHardy  * whitespace and the values. Whitespace in this context means any amount of
208ea45f12aSPatrick McHardy  * tabs, spaces and continuation lines, which are treated as a single whitespace
209ea45f12aSPatrick McHardy  * character.
21005e3ced2SPatrick McHardy  *
21105e3ced2SPatrick McHardy  * Some headers may appear multiple times. A comma seperated list of values is
21205e3ced2SPatrick McHardy  * equivalent to multiple headers.
213ea45f12aSPatrick McHardy  */
214ea45f12aSPatrick McHardy static const struct sip_header ct_sip_hdrs[] = {
21530f33e6dSPatrick McHardy 	[SIP_HDR_CSEQ]			= SIP_HDR("CSeq", NULL, NULL, digits_len),
216ea45f12aSPatrick McHardy 	[SIP_HDR_FROM]			= SIP_HDR("From", "f", "sip:", skp_epaddr_len),
217ea45f12aSPatrick McHardy 	[SIP_HDR_TO]			= SIP_HDR("To", "t", "sip:", skp_epaddr_len),
218ea45f12aSPatrick McHardy 	[SIP_HDR_CONTACT]		= SIP_HDR("Contact", "m", "sip:", skp_epaddr_len),
219ea45f12aSPatrick McHardy 	[SIP_HDR_VIA]			= SIP_HDR("Via", "v", "UDP ", epaddr_len),
220ea45f12aSPatrick McHardy 	[SIP_HDR_CONTENT_LENGTH]	= SIP_HDR("Content-Length", "l", NULL, digits_len),
221ea45f12aSPatrick McHardy };
222ea45f12aSPatrick McHardy 
223ea45f12aSPatrick McHardy static const char *sip_follow_continuation(const char *dptr, const char *limit)
2249fafcd7bSPatrick McHardy {
225ea45f12aSPatrick McHardy 	/* Walk past newline */
226ea45f12aSPatrick McHardy 	if (++dptr >= limit)
227ea45f12aSPatrick McHardy 		return NULL;
2289fafcd7bSPatrick McHardy 
229ea45f12aSPatrick McHardy 	/* Skip '\n' in CR LF */
230ea45f12aSPatrick McHardy 	if (*(dptr - 1) == '\r' && *dptr == '\n') {
231ea45f12aSPatrick McHardy 		if (++dptr >= limit)
232ea45f12aSPatrick McHardy 			return NULL;
233ea45f12aSPatrick McHardy 	}
2349fafcd7bSPatrick McHardy 
235ea45f12aSPatrick McHardy 	/* Continuation line? */
236ea45f12aSPatrick McHardy 	if (*dptr != ' ' && *dptr != '\t')
237ea45f12aSPatrick McHardy 		return NULL;
238ea45f12aSPatrick McHardy 
239ea45f12aSPatrick McHardy 	/* skip leading whitespace */
240ea45f12aSPatrick McHardy 	for (; dptr < limit; dptr++) {
241ea45f12aSPatrick McHardy 		if (*dptr != ' ' && *dptr != '\t')
242ea45f12aSPatrick McHardy 			break;
243ea45f12aSPatrick McHardy 	}
244ea45f12aSPatrick McHardy 	return dptr;
245ea45f12aSPatrick McHardy }
246ea45f12aSPatrick McHardy 
247ea45f12aSPatrick McHardy static const char *sip_skip_whitespace(const char *dptr, const char *limit)
248ea45f12aSPatrick McHardy {
249ea45f12aSPatrick McHardy 	for (; dptr < limit; dptr++) {
250ea45f12aSPatrick McHardy 		if (*dptr == ' ')
251ea45f12aSPatrick McHardy 			continue;
252ea45f12aSPatrick McHardy 		if (*dptr != '\r' && *dptr != '\n')
253ea45f12aSPatrick McHardy 			break;
254ea45f12aSPatrick McHardy 		dptr = sip_follow_continuation(dptr, limit);
255ea45f12aSPatrick McHardy 		if (dptr == NULL)
256ea45f12aSPatrick McHardy 			return NULL;
257ea45f12aSPatrick McHardy 	}
258ea45f12aSPatrick McHardy 	return dptr;
259ea45f12aSPatrick McHardy }
260ea45f12aSPatrick McHardy 
261ea45f12aSPatrick McHardy /* Search within a SIP header value, dealing with continuation lines */
262ea45f12aSPatrick McHardy static const char *ct_sip_header_search(const char *dptr, const char *limit,
263ea45f12aSPatrick McHardy 					const char *needle, unsigned int len)
264ea45f12aSPatrick McHardy {
265ea45f12aSPatrick McHardy 	for (limit -= len; dptr < limit; dptr++) {
266ea45f12aSPatrick McHardy 		if (*dptr == '\r' || *dptr == '\n') {
267ea45f12aSPatrick McHardy 			dptr = sip_follow_continuation(dptr, limit);
268ea45f12aSPatrick McHardy 			if (dptr == NULL)
269ea45f12aSPatrick McHardy 				break;
2709fafcd7bSPatrick McHardy 			continue;
2719fafcd7bSPatrick McHardy 		}
2729fafcd7bSPatrick McHardy 
273ea45f12aSPatrick McHardy 		if (strnicmp(dptr, needle, len) == 0)
274ea45f12aSPatrick McHardy 			return dptr;
275ea45f12aSPatrick McHardy 	}
276ea45f12aSPatrick McHardy 	return NULL;
277ea45f12aSPatrick McHardy }
278ea45f12aSPatrick McHardy 
279ea45f12aSPatrick McHardy int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
280ea45f12aSPatrick McHardy 		      unsigned int dataoff, unsigned int datalen,
281ea45f12aSPatrick McHardy 		      enum sip_header_types type,
282ea45f12aSPatrick McHardy 		      unsigned int *matchoff, unsigned int *matchlen)
283ea45f12aSPatrick McHardy {
284ea45f12aSPatrick McHardy 	const struct sip_header *hdr = &ct_sip_hdrs[type];
285ea45f12aSPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen;
286ea45f12aSPatrick McHardy 	int shift = 0;
287ea45f12aSPatrick McHardy 
288ea45f12aSPatrick McHardy 	for (dptr += dataoff; dptr < limit; dptr++) {
289ea45f12aSPatrick McHardy 		/* Find beginning of line */
290ea45f12aSPatrick McHardy 		if (*dptr != '\r' && *dptr != '\n')
291ea45f12aSPatrick McHardy 			continue;
292ea45f12aSPatrick McHardy 		if (++dptr >= limit)
293ea45f12aSPatrick McHardy 			break;
294ea45f12aSPatrick McHardy 		if (*(dptr - 1) == '\r' && *dptr == '\n') {
295ea45f12aSPatrick McHardy 			if (++dptr >= limit)
296ea45f12aSPatrick McHardy 				break;
297ea45f12aSPatrick McHardy 		}
298ea45f12aSPatrick McHardy 
299ea45f12aSPatrick McHardy 		/* Skip continuation lines */
300ea45f12aSPatrick McHardy 		if (*dptr == ' ' || *dptr == '\t')
301ea45f12aSPatrick McHardy 			continue;
302ea45f12aSPatrick McHardy 
303ea45f12aSPatrick McHardy 		/* Find header. Compact headers must be followed by a
304ea45f12aSPatrick McHardy 		 * non-alphabetic character to avoid mismatches. */
305ea45f12aSPatrick McHardy 		if (limit - dptr >= hdr->len &&
306ea45f12aSPatrick McHardy 		    strnicmp(dptr, hdr->name, hdr->len) == 0)
307ea45f12aSPatrick McHardy 			dptr += hdr->len;
308ea45f12aSPatrick McHardy 		else if (hdr->cname && limit - dptr >= hdr->clen + 1 &&
309ea45f12aSPatrick McHardy 			 strnicmp(dptr, hdr->cname, hdr->clen) == 0 &&
310ea45f12aSPatrick McHardy 			 !isalpha(*(dptr + hdr->clen + 1)))
311ea45f12aSPatrick McHardy 			dptr += hdr->clen;
312ea45f12aSPatrick McHardy 		else
313ea45f12aSPatrick McHardy 			continue;
314ea45f12aSPatrick McHardy 
315ea45f12aSPatrick McHardy 		/* Find and skip colon */
316ea45f12aSPatrick McHardy 		dptr = sip_skip_whitespace(dptr, limit);
317ea45f12aSPatrick McHardy 		if (dptr == NULL)
318ea45f12aSPatrick McHardy 			break;
319ea45f12aSPatrick McHardy 		if (*dptr != ':' || ++dptr >= limit)
320ea45f12aSPatrick McHardy 			break;
321ea45f12aSPatrick McHardy 
322ea45f12aSPatrick McHardy 		/* Skip whitespace after colon */
323ea45f12aSPatrick McHardy 		dptr = sip_skip_whitespace(dptr, limit);
324ea45f12aSPatrick McHardy 		if (dptr == NULL)
325ea45f12aSPatrick McHardy 			break;
326ea45f12aSPatrick McHardy 
327ea45f12aSPatrick McHardy 		*matchoff = dptr - start;
328ea45f12aSPatrick McHardy 		if (hdr->search) {
329ea45f12aSPatrick McHardy 			dptr = ct_sip_header_search(dptr, limit, hdr->search,
330ea45f12aSPatrick McHardy 						    hdr->slen);
331ea45f12aSPatrick McHardy 			if (!dptr)
332ea45f12aSPatrick McHardy 				return -1;
333ea45f12aSPatrick McHardy 			dptr += hdr->slen;
334ea45f12aSPatrick McHardy 		}
335ea45f12aSPatrick McHardy 
336ea45f12aSPatrick McHardy 		*matchlen = hdr->match_len(ct, dptr, limit, &shift);
3379fafcd7bSPatrick McHardy 		if (!*matchlen)
3389fafcd7bSPatrick McHardy 			return -1;
339ea45f12aSPatrick McHardy 		*matchoff = dptr - start + shift;
3409fafcd7bSPatrick McHardy 		return 1;
3419fafcd7bSPatrick McHardy 	}
3429fafcd7bSPatrick McHardy 	return 0;
3439fafcd7bSPatrick McHardy }
344ea45f12aSPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_get_header);
3459fafcd7bSPatrick McHardy 
34605e3ced2SPatrick McHardy /* Get next header field in a list of comma seperated values */
34705e3ced2SPatrick McHardy static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr,
34805e3ced2SPatrick McHardy 			      unsigned int dataoff, unsigned int datalen,
34905e3ced2SPatrick McHardy 			      enum sip_header_types type,
35005e3ced2SPatrick McHardy 			      unsigned int *matchoff, unsigned int *matchlen)
35105e3ced2SPatrick McHardy {
35205e3ced2SPatrick McHardy 	const struct sip_header *hdr = &ct_sip_hdrs[type];
35305e3ced2SPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen;
35405e3ced2SPatrick McHardy 	int shift = 0;
35505e3ced2SPatrick McHardy 
35605e3ced2SPatrick McHardy 	dptr += dataoff;
35705e3ced2SPatrick McHardy 
35805e3ced2SPatrick McHardy 	dptr = ct_sip_header_search(dptr, limit, ",", strlen(","));
35905e3ced2SPatrick McHardy 	if (!dptr)
36005e3ced2SPatrick McHardy 		return 0;
36105e3ced2SPatrick McHardy 
36205e3ced2SPatrick McHardy 	dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen);
36305e3ced2SPatrick McHardy 	if (!dptr)
36405e3ced2SPatrick McHardy 		return 0;
36505e3ced2SPatrick McHardy 	dptr += hdr->slen;
36605e3ced2SPatrick McHardy 
36705e3ced2SPatrick McHardy 	*matchoff = dptr - start;
36805e3ced2SPatrick McHardy 	*matchlen = hdr->match_len(ct, dptr, limit, &shift);
36905e3ced2SPatrick McHardy 	if (!*matchlen)
37005e3ced2SPatrick McHardy 		return -1;
37105e3ced2SPatrick McHardy 	*matchoff += shift;
37205e3ced2SPatrick McHardy 	return 1;
37305e3ced2SPatrick McHardy }
37405e3ced2SPatrick McHardy 
37505e3ced2SPatrick McHardy /* Walk through headers until a parsable one is found or no header of the
37605e3ced2SPatrick McHardy  * given type is left. */
37705e3ced2SPatrick McHardy static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr,
37805e3ced2SPatrick McHardy 			       unsigned int dataoff, unsigned int datalen,
37905e3ced2SPatrick McHardy 			       enum sip_header_types type, int *in_header,
38005e3ced2SPatrick McHardy 			       unsigned int *matchoff, unsigned int *matchlen)
38105e3ced2SPatrick McHardy {
38205e3ced2SPatrick McHardy 	int ret;
38305e3ced2SPatrick McHardy 
38405e3ced2SPatrick McHardy 	if (in_header && *in_header) {
38505e3ced2SPatrick McHardy 		while (1) {
38605e3ced2SPatrick McHardy 			ret = ct_sip_next_header(ct, dptr, dataoff, datalen,
38705e3ced2SPatrick McHardy 						 type, matchoff, matchlen);
38805e3ced2SPatrick McHardy 			if (ret > 0)
38905e3ced2SPatrick McHardy 				return ret;
39005e3ced2SPatrick McHardy 			if (ret == 0)
39105e3ced2SPatrick McHardy 				break;
39205e3ced2SPatrick McHardy 			dataoff += *matchoff;
39305e3ced2SPatrick McHardy 		}
39405e3ced2SPatrick McHardy 		*in_header = 0;
39505e3ced2SPatrick McHardy 	}
39605e3ced2SPatrick McHardy 
39705e3ced2SPatrick McHardy 	while (1) {
39805e3ced2SPatrick McHardy 		ret = ct_sip_get_header(ct, dptr, dataoff, datalen,
39905e3ced2SPatrick McHardy 					type, matchoff, matchlen);
40005e3ced2SPatrick McHardy 		if (ret > 0)
40105e3ced2SPatrick McHardy 			break;
40205e3ced2SPatrick McHardy 		if (ret == 0)
40305e3ced2SPatrick McHardy 			return ret;
40405e3ced2SPatrick McHardy 		dataoff += *matchoff;
40505e3ced2SPatrick McHardy 	}
40605e3ced2SPatrick McHardy 
40705e3ced2SPatrick McHardy 	if (in_header)
40805e3ced2SPatrick McHardy 		*in_header = 1;
40905e3ced2SPatrick McHardy 	return 1;
41005e3ced2SPatrick McHardy }
41105e3ced2SPatrick McHardy 
41205e3ced2SPatrick McHardy /* Locate a SIP header, parse the URI and return the offset and length of
41305e3ced2SPatrick McHardy  * the address as well as the address and port themselves. A stream of
41405e3ced2SPatrick McHardy  * headers can be parsed by handing in a non-NULL datalen and in_header
41505e3ced2SPatrick McHardy  * pointer.
41605e3ced2SPatrick McHardy  */
41705e3ced2SPatrick McHardy int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr,
41805e3ced2SPatrick McHardy 			    unsigned int *dataoff, unsigned int datalen,
41905e3ced2SPatrick McHardy 			    enum sip_header_types type, int *in_header,
42005e3ced2SPatrick McHardy 			    unsigned int *matchoff, unsigned int *matchlen,
42105e3ced2SPatrick McHardy 			    union nf_inet_addr *addr, __be16 *port)
42205e3ced2SPatrick McHardy {
42305e3ced2SPatrick McHardy 	const char *c, *limit = dptr + datalen;
42405e3ced2SPatrick McHardy 	unsigned int p;
42505e3ced2SPatrick McHardy 	int ret;
42605e3ced2SPatrick McHardy 
42705e3ced2SPatrick McHardy 	ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen,
42805e3ced2SPatrick McHardy 				  type, in_header, matchoff, matchlen);
42905e3ced2SPatrick McHardy 	WARN_ON(ret < 0);
43005e3ced2SPatrick McHardy 	if (ret == 0)
43105e3ced2SPatrick McHardy 		return ret;
43205e3ced2SPatrick McHardy 
43305e3ced2SPatrick McHardy 	if (!parse_addr(ct, dptr + *matchoff, &c, addr, limit))
43405e3ced2SPatrick McHardy 		return -1;
43505e3ced2SPatrick McHardy 	if (*c == ':') {
43605e3ced2SPatrick McHardy 		c++;
43705e3ced2SPatrick McHardy 		p = simple_strtoul(c, (char **)&c, 10);
43805e3ced2SPatrick McHardy 		if (p < 1024 || p > 65535)
43905e3ced2SPatrick McHardy 			return -1;
44005e3ced2SPatrick McHardy 		*port = htons(p);
44105e3ced2SPatrick McHardy 	} else
44205e3ced2SPatrick McHardy 		*port = htons(SIP_PORT);
44305e3ced2SPatrick McHardy 
44405e3ced2SPatrick McHardy 	if (dataoff)
44505e3ced2SPatrick McHardy 		*dataoff = c - dptr;
44605e3ced2SPatrick McHardy 	return 1;
44705e3ced2SPatrick McHardy }
44805e3ced2SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri);
44905e3ced2SPatrick McHardy 
4503e9b4600SPatrick McHardy /* SDP header parsing: a SDP session description contains an ordered set of
4513e9b4600SPatrick McHardy  * headers, starting with a section containing general session parameters,
4523e9b4600SPatrick McHardy  * optionally followed by multiple media descriptions.
4533e9b4600SPatrick McHardy  *
4543e9b4600SPatrick McHardy  * SDP headers always start at the beginning of a line. According to RFC 2327:
4553e9b4600SPatrick McHardy  * "The sequence CRLF (0x0d0a) is used to end a record, although parsers should
4563e9b4600SPatrick McHardy  * be tolerant and also accept records terminated with a single newline
4573e9b4600SPatrick McHardy  * character". We handle both cases.
4583e9b4600SPatrick McHardy  */
4593e9b4600SPatrick McHardy static const struct sip_header ct_sdp_hdrs[] = {
4603e9b4600SPatrick McHardy 	[SDP_HDR_VERSION]		= SDP_HDR("v=", NULL, digits_len),
4613e9b4600SPatrick McHardy 	[SDP_HDR_OWNER_IP4]		= SDP_HDR("o=", "IN IP4 ", epaddr_len),
4623e9b4600SPatrick McHardy 	[SDP_HDR_CONNECTION_IP4]	= SDP_HDR("c=", "IN IP4 ", epaddr_len),
4633e9b4600SPatrick McHardy 	[SDP_HDR_OWNER_IP6]		= SDP_HDR("o=", "IN IP6 ", epaddr_len),
4643e9b4600SPatrick McHardy 	[SDP_HDR_CONNECTION_IP6]	= SDP_HDR("c=", "IN IP6 ", epaddr_len),
4653e9b4600SPatrick McHardy 	[SDP_HDR_MEDIA]			= SDP_HDR("m=", "audio ", digits_len),
4663e9b4600SPatrick McHardy };
4673e9b4600SPatrick McHardy 
4683e9b4600SPatrick McHardy /* Linear string search within SDP header values */
4693e9b4600SPatrick McHardy static const char *ct_sdp_header_search(const char *dptr, const char *limit,
4703e9b4600SPatrick McHardy 					const char *needle, unsigned int len)
4713e9b4600SPatrick McHardy {
4723e9b4600SPatrick McHardy 	for (limit -= len; dptr < limit; dptr++) {
4733e9b4600SPatrick McHardy 		if (*dptr == '\r' || *dptr == '\n')
4743e9b4600SPatrick McHardy 			break;
4753e9b4600SPatrick McHardy 		if (strncmp(dptr, needle, len) == 0)
4763e9b4600SPatrick McHardy 			return dptr;
4773e9b4600SPatrick McHardy 	}
4783e9b4600SPatrick McHardy 	return NULL;
4793e9b4600SPatrick McHardy }
4803e9b4600SPatrick McHardy 
4813e9b4600SPatrick McHardy /* Locate a SDP header (optionally a substring within the header value),
4823e9b4600SPatrick McHardy  * optionally stopping at the first occurence of the term header, parse
4833e9b4600SPatrick McHardy  * it and return the offset and length of the data we're interested in.
4843e9b4600SPatrick McHardy  */
4853e9b4600SPatrick McHardy int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
4863e9b4600SPatrick McHardy 			  unsigned int dataoff, unsigned int datalen,
4873e9b4600SPatrick McHardy 			  enum sdp_header_types type,
4883e9b4600SPatrick McHardy 			  enum sdp_header_types term,
4893e9b4600SPatrick McHardy 			  unsigned int *matchoff, unsigned int *matchlen)
4903e9b4600SPatrick McHardy {
4913e9b4600SPatrick McHardy 	const struct sip_header *hdr = &ct_sdp_hdrs[type];
4923e9b4600SPatrick McHardy 	const struct sip_header *thdr = &ct_sdp_hdrs[term];
4933e9b4600SPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen;
4943e9b4600SPatrick McHardy 	int shift = 0;
4953e9b4600SPatrick McHardy 
4963e9b4600SPatrick McHardy 	for (dptr += dataoff; dptr < limit; dptr++) {
4973e9b4600SPatrick McHardy 		/* Find beginning of line */
4983e9b4600SPatrick McHardy 		if (*dptr != '\r' && *dptr != '\n')
4993e9b4600SPatrick McHardy 			continue;
5003e9b4600SPatrick McHardy 		if (++dptr >= limit)
5013e9b4600SPatrick McHardy 			break;
5023e9b4600SPatrick McHardy 		if (*(dptr - 1) == '\r' && *dptr == '\n') {
5033e9b4600SPatrick McHardy 			if (++dptr >= limit)
5043e9b4600SPatrick McHardy 				break;
5053e9b4600SPatrick McHardy 		}
5063e9b4600SPatrick McHardy 
5073e9b4600SPatrick McHardy 		if (term != SDP_HDR_UNSPEC &&
5083e9b4600SPatrick McHardy 		    limit - dptr >= thdr->len &&
5093e9b4600SPatrick McHardy 		    strnicmp(dptr, thdr->name, thdr->len) == 0)
5103e9b4600SPatrick McHardy 			break;
5113e9b4600SPatrick McHardy 		else if (limit - dptr >= hdr->len &&
5123e9b4600SPatrick McHardy 			 strnicmp(dptr, hdr->name, hdr->len) == 0)
5133e9b4600SPatrick McHardy 			dptr += hdr->len;
5143e9b4600SPatrick McHardy 		else
5153e9b4600SPatrick McHardy 			continue;
5163e9b4600SPatrick McHardy 
5173e9b4600SPatrick McHardy 		*matchoff = dptr - start;
5183e9b4600SPatrick McHardy 		if (hdr->search) {
5193e9b4600SPatrick McHardy 			dptr = ct_sdp_header_search(dptr, limit, hdr->search,
5203e9b4600SPatrick McHardy 						    hdr->slen);
5213e9b4600SPatrick McHardy 			if (!dptr)
5223e9b4600SPatrick McHardy 				return -1;
5233e9b4600SPatrick McHardy 			dptr += hdr->slen;
5243e9b4600SPatrick McHardy 		}
5253e9b4600SPatrick McHardy 
5263e9b4600SPatrick McHardy 		*matchlen = hdr->match_len(ct, dptr, limit, &shift);
5273e9b4600SPatrick McHardy 		if (!*matchlen)
5283e9b4600SPatrick McHardy 			return -1;
5293e9b4600SPatrick McHardy 		*matchoff = dptr - start + shift;
5303e9b4600SPatrick McHardy 		return 1;
5313e9b4600SPatrick McHardy 	}
5323e9b4600SPatrick McHardy 	return 0;
5333e9b4600SPatrick McHardy }
5343e9b4600SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header);
5353e9b4600SPatrick McHardy 
5363db05feaSHerbert Xu static int set_expected_rtp(struct sk_buff *skb,
537212440a7SPatrick McHardy 			    const char **dptr, unsigned int *datalen,
538212440a7SPatrick McHardy 			    union nf_inet_addr *addr, __be16 port)
5399fafcd7bSPatrick McHardy {
5409fafcd7bSPatrick McHardy 	struct nf_conntrack_expect *exp;
541212440a7SPatrick McHardy 	enum ip_conntrack_info ctinfo;
542212440a7SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
5439fafcd7bSPatrick McHardy 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
5449fafcd7bSPatrick McHardy 	int family = ct->tuplehash[!dir].tuple.src.l3num;
5459fafcd7bSPatrick McHardy 	int ret;
5469fafcd7bSPatrick McHardy 	typeof(nf_nat_sdp_hook) nf_nat_sdp;
5479fafcd7bSPatrick McHardy 
5486823645dSPatrick McHardy 	exp = nf_ct_expect_alloc(ct);
5499fafcd7bSPatrick McHardy 	if (exp == NULL)
5509fafcd7bSPatrick McHardy 		return NF_DROP;
5516002f266SPatrick McHardy 	nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, family,
5529fafcd7bSPatrick McHardy 			  &ct->tuplehash[!dir].tuple.src.u3, addr,
5539fafcd7bSPatrick McHardy 			  IPPROTO_UDP, NULL, &port);
5549fafcd7bSPatrick McHardy 
5559fafcd7bSPatrick McHardy 	nf_nat_sdp = rcu_dereference(nf_nat_sdp_hook);
5569fafcd7bSPatrick McHardy 	if (nf_nat_sdp && ct->status & IPS_NAT_MASK)
557212440a7SPatrick McHardy 		ret = nf_nat_sdp(skb, dptr, datalen, exp);
5589fafcd7bSPatrick McHardy 	else {
5596823645dSPatrick McHardy 		if (nf_ct_expect_related(exp) != 0)
5609fafcd7bSPatrick McHardy 			ret = NF_DROP;
5619fafcd7bSPatrick McHardy 		else
5629fafcd7bSPatrick McHardy 			ret = NF_ACCEPT;
5639fafcd7bSPatrick McHardy 	}
5646823645dSPatrick McHardy 	nf_ct_expect_put(exp);
5659fafcd7bSPatrick McHardy 
5669fafcd7bSPatrick McHardy 	return ret;
5679fafcd7bSPatrick McHardy }
5689fafcd7bSPatrick McHardy 
5697d3dd043SPatrick McHardy static int process_sdp(struct sk_buff *skb,
57030f33e6dSPatrick McHardy 		       const char **dptr, unsigned int *datalen,
57130f33e6dSPatrick McHardy 		       unsigned int cseq)
5727d3dd043SPatrick McHardy {
5737d3dd043SPatrick McHardy 	enum ip_conntrack_info ctinfo;
5747d3dd043SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
5757d3dd043SPatrick McHardy 	int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
5767d3dd043SPatrick McHardy 	unsigned int matchoff, matchlen;
5777d3dd043SPatrick McHardy 	union nf_inet_addr addr;
5787d3dd043SPatrick McHardy 	unsigned int port;
5797d3dd043SPatrick McHardy 	enum sdp_header_types type;
5807d3dd043SPatrick McHardy 
5817d3dd043SPatrick McHardy 	/* Get address and port from SDP packet. */
5827d3dd043SPatrick McHardy 	type = family == AF_INET ? SDP_HDR_CONNECTION_IP4 :
5837d3dd043SPatrick McHardy 				   SDP_HDR_CONNECTION_IP6;
5847d3dd043SPatrick McHardy 
5857d3dd043SPatrick McHardy 	if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
5867d3dd043SPatrick McHardy 				  type, SDP_HDR_UNSPEC,
5877d3dd043SPatrick McHardy 				  &matchoff, &matchlen) <= 0)
5887d3dd043SPatrick McHardy 		return NF_ACCEPT;
5897d3dd043SPatrick McHardy 
5907d3dd043SPatrick McHardy 	/* We'll drop only if there are parse problems. */
5917d3dd043SPatrick McHardy 	if (!parse_addr(ct, *dptr + matchoff, NULL, &addr, *dptr + *datalen))
5927d3dd043SPatrick McHardy 		return NF_DROP;
5937d3dd043SPatrick McHardy 
5947d3dd043SPatrick McHardy 	if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
5957d3dd043SPatrick McHardy 				  SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
5967d3dd043SPatrick McHardy 				  &matchoff, &matchlen) <= 0)
5977d3dd043SPatrick McHardy 		return NF_ACCEPT;
5987d3dd043SPatrick McHardy 
5997d3dd043SPatrick McHardy 	port = simple_strtoul(*dptr + matchoff, NULL, 10);
6007d3dd043SPatrick McHardy 	if (port < 1024 || port > 65535)
6017d3dd043SPatrick McHardy 		return NF_DROP;
6027d3dd043SPatrick McHardy 
6037d3dd043SPatrick McHardy 	return set_expected_rtp(skb, dptr, datalen, &addr, htons(port));
6047d3dd043SPatrick McHardy }
60530f33e6dSPatrick McHardy static int process_invite_response(struct sk_buff *skb,
60630f33e6dSPatrick McHardy 				   const char **dptr, unsigned int *datalen,
60730f33e6dSPatrick McHardy 				   unsigned int cseq, unsigned int code)
60830f33e6dSPatrick McHardy {
60930f33e6dSPatrick McHardy 	if ((code >= 100 && code <= 199) ||
61030f33e6dSPatrick McHardy 	    (code >= 200 && code <= 299))
61130f33e6dSPatrick McHardy 		return process_sdp(skb, dptr, datalen, cseq);
61230f33e6dSPatrick McHardy 
61330f33e6dSPatrick McHardy 	return NF_ACCEPT;
61430f33e6dSPatrick McHardy }
61530f33e6dSPatrick McHardy 
61630f33e6dSPatrick McHardy static int process_update_response(struct sk_buff *skb,
61730f33e6dSPatrick McHardy 				   const char **dptr, unsigned int *datalen,
61830f33e6dSPatrick McHardy 				   unsigned int cseq, unsigned int code)
61930f33e6dSPatrick McHardy {
62030f33e6dSPatrick McHardy 	if ((code >= 100 && code <= 199) ||
62130f33e6dSPatrick McHardy 	    (code >= 200 && code <= 299))
62230f33e6dSPatrick McHardy 		return process_sdp(skb, dptr, datalen, cseq);
62330f33e6dSPatrick McHardy 
62430f33e6dSPatrick McHardy 	return NF_ACCEPT;
62530f33e6dSPatrick McHardy }
62630f33e6dSPatrick McHardy 
627595a8ecbSPatrick McHardy static int process_prack_response(struct sk_buff *skb,
628595a8ecbSPatrick McHardy 				  const char **dptr, unsigned int *datalen,
629595a8ecbSPatrick McHardy 				  unsigned int cseq, unsigned int code)
630595a8ecbSPatrick McHardy {
631595a8ecbSPatrick McHardy 	if ((code >= 100 && code <= 199) ||
632595a8ecbSPatrick McHardy 	    (code >= 200 && code <= 299))
633595a8ecbSPatrick McHardy 		return process_sdp(skb, dptr, datalen, cseq);
634595a8ecbSPatrick McHardy 
635595a8ecbSPatrick McHardy 	return NF_ACCEPT;
636595a8ecbSPatrick McHardy }
637595a8ecbSPatrick McHardy 
63830f33e6dSPatrick McHardy static const struct sip_handler sip_handlers[] = {
63930f33e6dSPatrick McHardy 	SIP_HANDLER("INVITE", process_sdp, process_invite_response),
64030f33e6dSPatrick McHardy 	SIP_HANDLER("UPDATE", process_sdp, process_update_response),
641595a8ecbSPatrick McHardy 	SIP_HANDLER("ACK", process_sdp, NULL),
642595a8ecbSPatrick McHardy 	SIP_HANDLER("PRACK", process_sdp, process_prack_response),
64330f33e6dSPatrick McHardy };
64430f33e6dSPatrick McHardy 
64530f33e6dSPatrick McHardy static int process_sip_response(struct sk_buff *skb,
64630f33e6dSPatrick McHardy 				const char **dptr, unsigned int *datalen)
64730f33e6dSPatrick McHardy {
64830f33e6dSPatrick McHardy 	static const struct sip_handler *handler;
64930f33e6dSPatrick McHardy 	enum ip_conntrack_info ctinfo;
65030f33e6dSPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
65130f33e6dSPatrick McHardy 	unsigned int matchoff, matchlen;
65230f33e6dSPatrick McHardy 	unsigned int code, cseq, dataoff, i;
65330f33e6dSPatrick McHardy 
65430f33e6dSPatrick McHardy 	if (*datalen < strlen("SIP/2.0 200"))
65530f33e6dSPatrick McHardy 		return NF_ACCEPT;
65630f33e6dSPatrick McHardy 	code = simple_strtoul(*dptr + strlen("SIP/2.0 "), NULL, 10);
65730f33e6dSPatrick McHardy 	if (!code)
65830f33e6dSPatrick McHardy 		return NF_DROP;
65930f33e6dSPatrick McHardy 
66030f33e6dSPatrick McHardy 	if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
66130f33e6dSPatrick McHardy 			      &matchoff, &matchlen) <= 0)
66230f33e6dSPatrick McHardy 		return NF_DROP;
66330f33e6dSPatrick McHardy 	cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
66430f33e6dSPatrick McHardy 	if (!cseq)
66530f33e6dSPatrick McHardy 		return NF_DROP;
66630f33e6dSPatrick McHardy 	dataoff = matchoff + matchlen + 1;
66730f33e6dSPatrick McHardy 
66830f33e6dSPatrick McHardy 	for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
66930f33e6dSPatrick McHardy 		handler = &sip_handlers[i];
67030f33e6dSPatrick McHardy 		if (handler->response == NULL)
67130f33e6dSPatrick McHardy 			continue;
67230f33e6dSPatrick McHardy 		if (*datalen < dataoff + handler->len ||
67330f33e6dSPatrick McHardy 		    strnicmp(*dptr + dataoff, handler->method, handler->len))
67430f33e6dSPatrick McHardy 			continue;
67530f33e6dSPatrick McHardy 		return handler->response(skb, dptr, datalen, cseq, code);
67630f33e6dSPatrick McHardy 	}
67730f33e6dSPatrick McHardy 	return NF_ACCEPT;
67830f33e6dSPatrick McHardy }
67930f33e6dSPatrick McHardy 
68030f33e6dSPatrick McHardy static int process_sip_request(struct sk_buff *skb,
68130f33e6dSPatrick McHardy 			       const char **dptr, unsigned int *datalen)
68230f33e6dSPatrick McHardy {
68330f33e6dSPatrick McHardy 	static const struct sip_handler *handler;
68430f33e6dSPatrick McHardy 	enum ip_conntrack_info ctinfo;
68530f33e6dSPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
68630f33e6dSPatrick McHardy 	unsigned int matchoff, matchlen;
68730f33e6dSPatrick McHardy 	unsigned int cseq, i;
68830f33e6dSPatrick McHardy 
68930f33e6dSPatrick McHardy 	for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
69030f33e6dSPatrick McHardy 		handler = &sip_handlers[i];
69130f33e6dSPatrick McHardy 		if (handler->request == NULL)
69230f33e6dSPatrick McHardy 			continue;
69330f33e6dSPatrick McHardy 		if (*datalen < handler->len ||
69430f33e6dSPatrick McHardy 		    strnicmp(*dptr, handler->method, handler->len))
69530f33e6dSPatrick McHardy 			continue;
69630f33e6dSPatrick McHardy 
69730f33e6dSPatrick McHardy 		if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
69830f33e6dSPatrick McHardy 				      &matchoff, &matchlen) <= 0)
69930f33e6dSPatrick McHardy 			return NF_DROP;
70030f33e6dSPatrick McHardy 		cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
70130f33e6dSPatrick McHardy 		if (!cseq)
70230f33e6dSPatrick McHardy 			return NF_DROP;
70330f33e6dSPatrick McHardy 
70430f33e6dSPatrick McHardy 		return handler->request(skb, dptr, datalen, cseq);
70530f33e6dSPatrick McHardy 	}
70630f33e6dSPatrick McHardy 	return NF_ACCEPT;
70730f33e6dSPatrick McHardy }
7087d3dd043SPatrick McHardy 
7093db05feaSHerbert Xu static int sip_help(struct sk_buff *skb,
7109fafcd7bSPatrick McHardy 		    unsigned int protoff,
7119fafcd7bSPatrick McHardy 		    struct nf_conn *ct,
7129fafcd7bSPatrick McHardy 		    enum ip_conntrack_info ctinfo)
7139fafcd7bSPatrick McHardy {
7149fafcd7bSPatrick McHardy 	unsigned int dataoff, datalen;
7159fafcd7bSPatrick McHardy 	const char *dptr;
71633cb1e9aSPatrick McHardy 	int ret;
7179fafcd7bSPatrick McHardy 	typeof(nf_nat_sip_hook) nf_nat_sip;
7189fafcd7bSPatrick McHardy 
7199fafcd7bSPatrick McHardy 	/* No Data ? */
7209fafcd7bSPatrick McHardy 	dataoff = protoff + sizeof(struct udphdr);
7213db05feaSHerbert Xu 	if (dataoff >= skb->len)
7229fafcd7bSPatrick McHardy 		return NF_ACCEPT;
7239fafcd7bSPatrick McHardy 
7243db05feaSHerbert Xu 	nf_ct_refresh(ct, skb, sip_timeout * HZ);
7259fafcd7bSPatrick McHardy 
7263db05feaSHerbert Xu 	if (!skb_is_nonlinear(skb))
7273db05feaSHerbert Xu 		dptr = skb->data + dataoff;
7289fafcd7bSPatrick McHardy 	else {
7290d53778eSPatrick McHardy 		pr_debug("Copy of skbuff not supported yet.\n");
7307d3dd043SPatrick McHardy 		return NF_ACCEPT;
7319fafcd7bSPatrick McHardy 	}
7329fafcd7bSPatrick McHardy 
7333db05feaSHerbert Xu 	datalen = skb->len - dataoff;
734779382ebSPatrick McHardy 	if (datalen < strlen("SIP/2.0 200"))
7357d3dd043SPatrick McHardy 		return NF_ACCEPT;
7369fafcd7bSPatrick McHardy 
73730f33e6dSPatrick McHardy 	if (strnicmp(dptr, "SIP/2.0 ", strlen("SIP/2.0 ")) != 0)
73833cb1e9aSPatrick McHardy 		ret = process_sip_request(skb, &dptr, &datalen);
73930f33e6dSPatrick McHardy 	else
74033cb1e9aSPatrick McHardy 		ret = process_sip_response(skb, &dptr, &datalen);
74133cb1e9aSPatrick McHardy 
74233cb1e9aSPatrick McHardy 	if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
74333cb1e9aSPatrick McHardy 		nf_nat_sip = rcu_dereference(nf_nat_sip_hook);
74433cb1e9aSPatrick McHardy 		if (nf_nat_sip && !nf_nat_sip(skb, &dptr, &datalen))
74533cb1e9aSPatrick McHardy 			ret = NF_DROP;
74633cb1e9aSPatrick McHardy 	}
74733cb1e9aSPatrick McHardy 
74833cb1e9aSPatrick McHardy 	return ret;
7499fafcd7bSPatrick McHardy }
7509fafcd7bSPatrick McHardy 
7519fafcd7bSPatrick McHardy static struct nf_conntrack_helper sip[MAX_PORTS][2] __read_mostly;
7529fafcd7bSPatrick McHardy static char sip_names[MAX_PORTS][2][sizeof("sip-65535")] __read_mostly;
7539fafcd7bSPatrick McHardy 
7546002f266SPatrick McHardy static const struct nf_conntrack_expect_policy sip_exp_policy = {
7556002f266SPatrick McHardy 	.max_expected	= 2,
7566002f266SPatrick McHardy 	.timeout	= 3 * 60,
7576002f266SPatrick McHardy };
7586002f266SPatrick McHardy 
7599fafcd7bSPatrick McHardy static void nf_conntrack_sip_fini(void)
7609fafcd7bSPatrick McHardy {
7619fafcd7bSPatrick McHardy 	int i, j;
7629fafcd7bSPatrick McHardy 
7639fafcd7bSPatrick McHardy 	for (i = 0; i < ports_c; i++) {
7649fafcd7bSPatrick McHardy 		for (j = 0; j < 2; j++) {
7659fafcd7bSPatrick McHardy 			if (sip[i][j].me == NULL)
7669fafcd7bSPatrick McHardy 				continue;
7679fafcd7bSPatrick McHardy 			nf_conntrack_helper_unregister(&sip[i][j]);
7689fafcd7bSPatrick McHardy 		}
7699fafcd7bSPatrick McHardy 	}
7709fafcd7bSPatrick McHardy }
7719fafcd7bSPatrick McHardy 
7729fafcd7bSPatrick McHardy static int __init nf_conntrack_sip_init(void)
7739fafcd7bSPatrick McHardy {
7749fafcd7bSPatrick McHardy 	int i, j, ret;
7759fafcd7bSPatrick McHardy 	char *tmpname;
7769fafcd7bSPatrick McHardy 
7779fafcd7bSPatrick McHardy 	if (ports_c == 0)
7789fafcd7bSPatrick McHardy 		ports[ports_c++] = SIP_PORT;
7799fafcd7bSPatrick McHardy 
7809fafcd7bSPatrick McHardy 	for (i = 0; i < ports_c; i++) {
7819fafcd7bSPatrick McHardy 		memset(&sip[i], 0, sizeof(sip[i]));
7829fafcd7bSPatrick McHardy 
7839fafcd7bSPatrick McHardy 		sip[i][0].tuple.src.l3num = AF_INET;
7849fafcd7bSPatrick McHardy 		sip[i][1].tuple.src.l3num = AF_INET6;
7859fafcd7bSPatrick McHardy 		for (j = 0; j < 2; j++) {
7869fafcd7bSPatrick McHardy 			sip[i][j].tuple.dst.protonum = IPPROTO_UDP;
7879fafcd7bSPatrick McHardy 			sip[i][j].tuple.src.u.udp.port = htons(ports[i]);
7886002f266SPatrick McHardy 			sip[i][j].expect_policy = &sip_exp_policy;
7899fafcd7bSPatrick McHardy 			sip[i][j].me = THIS_MODULE;
7909fafcd7bSPatrick McHardy 			sip[i][j].help = sip_help;
7919fafcd7bSPatrick McHardy 
7929fafcd7bSPatrick McHardy 			tmpname = &sip_names[i][j][0];
7939fafcd7bSPatrick McHardy 			if (ports[i] == SIP_PORT)
7949fafcd7bSPatrick McHardy 				sprintf(tmpname, "sip");
7959fafcd7bSPatrick McHardy 			else
7969fafcd7bSPatrick McHardy 				sprintf(tmpname, "sip-%u", i);
7979fafcd7bSPatrick McHardy 			sip[i][j].name = tmpname;
7989fafcd7bSPatrick McHardy 
7990d53778eSPatrick McHardy 			pr_debug("port #%u: %u\n", i, ports[i]);
8009fafcd7bSPatrick McHardy 
8019fafcd7bSPatrick McHardy 			ret = nf_conntrack_helper_register(&sip[i][j]);
8029fafcd7bSPatrick McHardy 			if (ret) {
8039fafcd7bSPatrick McHardy 				printk("nf_ct_sip: failed to register helper "
8049fafcd7bSPatrick McHardy 				       "for pf: %u port: %u\n",
8059fafcd7bSPatrick McHardy 				       sip[i][j].tuple.src.l3num, ports[i]);
8069fafcd7bSPatrick McHardy 				nf_conntrack_sip_fini();
8079fafcd7bSPatrick McHardy 				return ret;
8089fafcd7bSPatrick McHardy 			}
8099fafcd7bSPatrick McHardy 		}
8109fafcd7bSPatrick McHardy 	}
8119fafcd7bSPatrick McHardy 	return 0;
8129fafcd7bSPatrick McHardy }
8139fafcd7bSPatrick McHardy 
8149fafcd7bSPatrick McHardy module_init(nf_conntrack_sip_init);
8159fafcd7bSPatrick McHardy module_exit(nf_conntrack_sip_fini);
816