1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29fafcd7bSPatrick McHardy /* SIP extension for IP connection tracking.
39fafcd7bSPatrick McHardy  *
49fafcd7bSPatrick McHardy  * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
59fafcd7bSPatrick McHardy  * based on RR's ip_conntrack_ftp.c and other modules.
6f49e1aa1SPatrick McHardy  * (C) 2007 United Security Providers
7f49e1aa1SPatrick McHardy  * (C) 2007, 2008 Patrick McHardy <kaber@trash.net>
89fafcd7bSPatrick McHardy  */
99fafcd7bSPatrick McHardy 
10ad6d9503SPablo Neira Ayuso #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11ad6d9503SPablo Neira Ayuso 
129fafcd7bSPatrick McHardy #include <linux/module.h>
139fafcd7bSPatrick McHardy #include <linux/ctype.h>
149fafcd7bSPatrick McHardy #include <linux/skbuff.h>
159fafcd7bSPatrick McHardy #include <linux/inet.h>
169fafcd7bSPatrick McHardy #include <linux/in.h>
179fafcd7bSPatrick McHardy #include <linux/udp.h>
18f5b321bdSPatrick McHardy #include <linux/tcp.h>
191863f096SYasuyuki Kozakai #include <linux/netfilter.h>
2029b0b5d5SAlin Nastac #include <linux/netfilter_ipv4.h>
2129b0b5d5SAlin Nastac #include <linux/netfilter_ipv6.h>
229fafcd7bSPatrick McHardy 
239fafcd7bSPatrick McHardy #include <net/netfilter/nf_conntrack.h>
249467ee38SPatrick McHardy #include <net/netfilter/nf_conntrack_core.h>
259fafcd7bSPatrick McHardy #include <net/netfilter/nf_conntrack_expect.h>
269fafcd7bSPatrick McHardy #include <net/netfilter/nf_conntrack_helper.h>
275d0aa2ccSPatrick McHardy #include <net/netfilter/nf_conntrack_zones.h>
289fafcd7bSPatrick McHardy #include <linux/netfilter/nf_conntrack_sip.h>
299fafcd7bSPatrick McHardy 
3008010a21SFlavio Leitner #define HELPER_NAME "sip"
3108010a21SFlavio Leitner 
329fafcd7bSPatrick McHardy MODULE_LICENSE("GPL");
339fafcd7bSPatrick McHardy MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
349fafcd7bSPatrick McHardy MODULE_DESCRIPTION("SIP connection tracking helper");
359fafcd7bSPatrick McHardy MODULE_ALIAS("ip_conntrack_sip");
3608010a21SFlavio Leitner MODULE_ALIAS_NFCT_HELPER(HELPER_NAME);
379fafcd7bSPatrick McHardy 
389fafcd7bSPatrick McHardy #define MAX_PORTS	8
399fafcd7bSPatrick McHardy static unsigned short ports[MAX_PORTS];
402f0d2f10SStephen Hemminger static unsigned int ports_c;
419fafcd7bSPatrick McHardy module_param_array(ports, ushort, &ports_c, 0400);
429fafcd7bSPatrick McHardy MODULE_PARM_DESC(ports, "port numbers of SIP servers");
439fafcd7bSPatrick McHardy 
449fafcd7bSPatrick McHardy static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT;
459fafcd7bSPatrick McHardy module_param(sip_timeout, uint, 0600);
469fafcd7bSPatrick McHardy MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session");
479fafcd7bSPatrick McHardy 
480f32a40fSPatrick McHardy static int sip_direct_signalling __read_mostly = 1;
490f32a40fSPatrick McHardy module_param(sip_direct_signalling, int, 0600);
500f32a40fSPatrick McHardy MODULE_PARM_DESC(sip_direct_signalling, "expect incoming calls from registrar "
510f32a40fSPatrick McHardy 					"only (default 1)");
520f32a40fSPatrick McHardy 
53d901a936SPatrick McHardy static int sip_direct_media __read_mostly = 1;
54d901a936SPatrick McHardy module_param(sip_direct_media, int, 0600);
55d901a936SPatrick McHardy MODULE_PARM_DESC(sip_direct_media, "Expect Media streams between signalling "
56d901a936SPatrick McHardy 				   "endpoints only (default 1)");
57d901a936SPatrick McHardy 
58a3419ce3SAlin Nastac static int sip_external_media __read_mostly = 0;
59a3419ce3SAlin Nastac module_param(sip_external_media, int, 0600);
60a3419ce3SAlin Nastac MODULE_PARM_DESC(sip_external_media, "Expect Media streams between external "
61a3419ce3SAlin Nastac 				     "endpoints (default 0)");
62a3419ce3SAlin Nastac 
636976890eSFlorian Westphal const struct nf_nat_sip_hooks __rcu *nf_nat_sip_hooks;
64180cf72fSholger@eitzenberger.org EXPORT_SYMBOL_GPL(nf_nat_sip_hooks);
659fafcd7bSPatrick McHardy 
string_len(const struct nf_conn * ct,const char * dptr,const char * limit,int * shift)66ac367740SPatrick McHardy static int string_len(const struct nf_conn *ct, const char *dptr,
67ac367740SPatrick McHardy 		      const char *limit, int *shift)
68ac367740SPatrick McHardy {
69ac367740SPatrick McHardy 	int len = 0;
70ac367740SPatrick McHardy 
71ac367740SPatrick McHardy 	while (dptr < limit && isalpha(*dptr)) {
72ac367740SPatrick McHardy 		dptr++;
73ac367740SPatrick McHardy 		len++;
74ac367740SPatrick McHardy 	}
75ac367740SPatrick McHardy 	return len;
76ac367740SPatrick McHardy }
77ac367740SPatrick McHardy 
digits_len(const struct nf_conn * ct,const char * dptr,const char * limit,int * shift)7813f7d63cSJan Engelhardt static int digits_len(const struct nf_conn *ct, const char *dptr,
799fafcd7bSPatrick McHardy 		      const char *limit, int *shift)
809fafcd7bSPatrick McHardy {
819fafcd7bSPatrick McHardy 	int len = 0;
82b1ec488bSPatrick McHardy 	while (dptr < limit && isdigit(*dptr)) {
839fafcd7bSPatrick McHardy 		dptr++;
849fafcd7bSPatrick McHardy 		len++;
859fafcd7bSPatrick McHardy 	}
869fafcd7bSPatrick McHardy 	return len;
879fafcd7bSPatrick McHardy }
889fafcd7bSPatrick McHardy 
iswordc(const char c)89001985b2SSimon Horman static int iswordc(const char c)
90001985b2SSimon Horman {
91001985b2SSimon Horman 	if (isalnum(c) || c == '!' || c == '"' || c == '%' ||
92f0608ceaSMarco Angaroni 	    (c >= '(' && c <= '+') || c == ':' || c == '<' || c == '>' ||
93001985b2SSimon Horman 	    c == '?' || (c >= '[' && c <= ']') || c == '_' || c == '`' ||
94f0608ceaSMarco Angaroni 	    c == '{' || c == '}' || c == '~' || (c >= '-' && c <= '/') ||
95f0608ceaSMarco Angaroni 	    c == '\'')
96001985b2SSimon Horman 		return 1;
97001985b2SSimon Horman 	return 0;
98001985b2SSimon Horman }
99001985b2SSimon Horman 
word_len(const char * dptr,const char * limit)100001985b2SSimon Horman static int word_len(const char *dptr, const char *limit)
101001985b2SSimon Horman {
102001985b2SSimon Horman 	int len = 0;
103001985b2SSimon Horman 	while (dptr < limit && iswordc(*dptr)) {
104001985b2SSimon Horman 		dptr++;
105001985b2SSimon Horman 		len++;
106001985b2SSimon Horman 	}
107001985b2SSimon Horman 	return len;
108001985b2SSimon Horman }
109001985b2SSimon Horman 
callid_len(const struct nf_conn * ct,const char * dptr,const char * limit,int * shift)110001985b2SSimon Horman static int callid_len(const struct nf_conn *ct, const char *dptr,
111001985b2SSimon Horman 		      const char *limit, int *shift)
112001985b2SSimon Horman {
113001985b2SSimon Horman 	int len, domain_len;
114001985b2SSimon Horman 
115001985b2SSimon Horman 	len = word_len(dptr, limit);
116001985b2SSimon Horman 	dptr += len;
117001985b2SSimon Horman 	if (!len || dptr == limit || *dptr != '@')
118001985b2SSimon Horman 		return len;
119001985b2SSimon Horman 	dptr++;
120001985b2SSimon Horman 	len++;
121001985b2SSimon Horman 
122001985b2SSimon Horman 	domain_len = word_len(dptr, limit);
123001985b2SSimon Horman 	if (!domain_len)
124001985b2SSimon Horman 		return 0;
125001985b2SSimon Horman 	return len + domain_len;
126001985b2SSimon Horman }
127001985b2SSimon Horman 
1280d0ab037SPatrick McHardy /* get media type + port length */
media_len(const struct nf_conn * ct,const char * dptr,const char * limit,int * shift)1290d0ab037SPatrick McHardy static int media_len(const struct nf_conn *ct, const char *dptr,
1300d0ab037SPatrick McHardy 		     const char *limit, int *shift)
1310d0ab037SPatrick McHardy {
1320d0ab037SPatrick McHardy 	int len = string_len(ct, dptr, limit, shift);
1330d0ab037SPatrick McHardy 
1340d0ab037SPatrick McHardy 	dptr += len;
1350d0ab037SPatrick McHardy 	if (dptr >= limit || *dptr != ' ')
1360d0ab037SPatrick McHardy 		return 0;
1370d0ab037SPatrick McHardy 	len++;
1380d0ab037SPatrick McHardy 	dptr++;
1390d0ab037SPatrick McHardy 
1400d0ab037SPatrick McHardy 	return len + digits_len(ct, dptr, limit, shift);
1410d0ab037SPatrick McHardy }
1420d0ab037SPatrick McHardy 
sip_parse_addr(const struct nf_conn * ct,const char * cp,const char ** endp,union nf_inet_addr * addr,const char * limit,bool delim)14302b69cbdSPatrick McHardy static int sip_parse_addr(const struct nf_conn *ct, const char *cp,
14413f7d63cSJan Engelhardt 			  const char **endp, union nf_inet_addr *addr,
14502b69cbdSPatrick McHardy 			  const char *limit, bool delim)
1469fafcd7bSPatrick McHardy {
1479fafcd7bSPatrick McHardy 	const char *end;
14802b69cbdSPatrick McHardy 	int ret;
1499fafcd7bSPatrick McHardy 
1505adbb9fbSSimon Horman 	if (!ct)
1515adbb9fbSSimon Horman 		return 0;
1525adbb9fbSSimon Horman 
153fa913ddfSPatrick McHardy 	memset(addr, 0, sizeof(*addr));
1545e8fbe2aSPatrick McHardy 	switch (nf_ct_l3num(ct)) {
1559fafcd7bSPatrick McHardy 	case AF_INET:
1569fafcd7bSPatrick McHardy 		ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
15702b69cbdSPatrick McHardy 		if (ret == 0)
15802b69cbdSPatrick McHardy 			return 0;
1599fafcd7bSPatrick McHardy 		break;
1609fafcd7bSPatrick McHardy 	case AF_INET6:
16102b69cbdSPatrick McHardy 		if (cp < limit && *cp == '[')
16202b69cbdSPatrick McHardy 			cp++;
16302b69cbdSPatrick McHardy 		else if (delim)
16402b69cbdSPatrick McHardy 			return 0;
16502b69cbdSPatrick McHardy 
1669fafcd7bSPatrick McHardy 		ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
16702b69cbdSPatrick McHardy 		if (ret == 0)
16802b69cbdSPatrick McHardy 			return 0;
16902b69cbdSPatrick McHardy 
17002b69cbdSPatrick McHardy 		if (end < limit && *end == ']')
17102b69cbdSPatrick McHardy 			end++;
17202b69cbdSPatrick McHardy 		else if (delim)
17302b69cbdSPatrick McHardy 			return 0;
1749fafcd7bSPatrick McHardy 		break;
1759fafcd7bSPatrick McHardy 	default:
1769fafcd7bSPatrick McHardy 		BUG();
1779fafcd7bSPatrick McHardy 	}
1789fafcd7bSPatrick McHardy 
1799fafcd7bSPatrick McHardy 	if (endp)
1809fafcd7bSPatrick McHardy 		*endp = end;
1819fafcd7bSPatrick McHardy 	return 1;
1829fafcd7bSPatrick McHardy }
1839fafcd7bSPatrick McHardy 
1849fafcd7bSPatrick McHardy /* skip ip address. returns its length. */
epaddr_len(const struct nf_conn * ct,const char * dptr,const char * limit,int * shift)18513f7d63cSJan Engelhardt static int epaddr_len(const struct nf_conn *ct, const char *dptr,
1869fafcd7bSPatrick McHardy 		      const char *limit, int *shift)
1879fafcd7bSPatrick McHardy {
188643a2c15SJan Engelhardt 	union nf_inet_addr addr;
1899fafcd7bSPatrick McHardy 	const char *aux = dptr;
1909fafcd7bSPatrick McHardy 
19102b69cbdSPatrick McHardy 	if (!sip_parse_addr(ct, dptr, &dptr, &addr, limit, true)) {
1920d53778eSPatrick McHardy 		pr_debug("ip: %s parse failed.!\n", dptr);
1939fafcd7bSPatrick McHardy 		return 0;
1949fafcd7bSPatrick McHardy 	}
1959fafcd7bSPatrick McHardy 
1969fafcd7bSPatrick McHardy 	/* Port number */
1979fafcd7bSPatrick McHardy 	if (*dptr == ':') {
1989fafcd7bSPatrick McHardy 		dptr++;
1999fafcd7bSPatrick McHardy 		dptr += digits_len(ct, dptr, limit, shift);
2009fafcd7bSPatrick McHardy 	}
2019fafcd7bSPatrick McHardy 	return dptr - aux;
2029fafcd7bSPatrick McHardy }
2039fafcd7bSPatrick McHardy 
2049fafcd7bSPatrick McHardy /* get address length, skiping user info. */
skp_epaddr_len(const struct nf_conn * ct,const char * dptr,const char * limit,int * shift)20513f7d63cSJan Engelhardt static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr,
2069fafcd7bSPatrick McHardy 			  const char *limit, int *shift)
2079fafcd7bSPatrick McHardy {
208aa584edaSPatrick McHardy 	const char *start = dptr;
2099fafcd7bSPatrick McHardy 	int s = *shift;
2109fafcd7bSPatrick McHardy 
2117da5bfbbSLars Immisch 	/* Search for @, but stop at the end of the line.
2127da5bfbbSLars Immisch 	 * We are inside a sip: URI, so we don't need to worry about
2137da5bfbbSLars Immisch 	 * continuation lines. */
214b1ec488bSPatrick McHardy 	while (dptr < limit &&
2157da5bfbbSLars Immisch 	       *dptr != '@' && *dptr != '\r' && *dptr != '\n') {
2169fafcd7bSPatrick McHardy 		(*shift)++;
2177da5bfbbSLars Immisch 		dptr++;
2187da5bfbbSLars Immisch 	}
2199fafcd7bSPatrick McHardy 
220b1ec488bSPatrick McHardy 	if (dptr < limit && *dptr == '@') {
2219fafcd7bSPatrick McHardy 		dptr++;
2229fafcd7bSPatrick McHardy 		(*shift)++;
223aa584edaSPatrick McHardy 	} else {
224aa584edaSPatrick McHardy 		dptr = start;
2259fafcd7bSPatrick McHardy 		*shift = s;
226aa584edaSPatrick McHardy 	}
2279fafcd7bSPatrick McHardy 
2289fafcd7bSPatrick McHardy 	return epaddr_len(ct, dptr, limit, shift);
2299fafcd7bSPatrick McHardy }
2309fafcd7bSPatrick McHardy 
231ac367740SPatrick McHardy /* Parse a SIP request line of the form:
232ac367740SPatrick McHardy  *
233ac367740SPatrick McHardy  * Request-Line = Method SP Request-URI SP SIP-Version CRLF
234ac367740SPatrick McHardy  *
235ac367740SPatrick McHardy  * and return the offset and length of the address contained in the Request-URI.
236ac367740SPatrick McHardy  */
ct_sip_parse_request(const struct nf_conn * ct,const char * dptr,unsigned int datalen,unsigned int * matchoff,unsigned int * matchlen,union nf_inet_addr * addr,__be16 * port)237ac367740SPatrick McHardy int ct_sip_parse_request(const struct nf_conn *ct,
238ac367740SPatrick McHardy 			 const char *dptr, unsigned int datalen,
239624f8b7bSPatrick McHardy 			 unsigned int *matchoff, unsigned int *matchlen,
240624f8b7bSPatrick McHardy 			 union nf_inet_addr *addr, __be16 *port)
241ac367740SPatrick McHardy {
242624f8b7bSPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen, *end;
243ac367740SPatrick McHardy 	unsigned int mlen;
244624f8b7bSPatrick McHardy 	unsigned int p;
245ac367740SPatrick McHardy 	int shift = 0;
246ac367740SPatrick McHardy 
247ac367740SPatrick McHardy 	/* Skip method and following whitespace */
248ac367740SPatrick McHardy 	mlen = string_len(ct, dptr, limit, NULL);
249ac367740SPatrick McHardy 	if (!mlen)
250ac367740SPatrick McHardy 		return 0;
251ac367740SPatrick McHardy 	dptr += mlen;
252ac367740SPatrick McHardy 	if (++dptr >= limit)
253ac367740SPatrick McHardy 		return 0;
254ac367740SPatrick McHardy 
255ac367740SPatrick McHardy 	/* Find SIP URI */
25654101f4fSPatrick McHardy 	for (; dptr < limit - strlen("sip:"); dptr++) {
257ac367740SPatrick McHardy 		if (*dptr == '\r' || *dptr == '\n')
258ac367740SPatrick McHardy 			return -1;
25918082746SRasmus Villemoes 		if (strncasecmp(dptr, "sip:", strlen("sip:")) == 0) {
26054101f4fSPatrick McHardy 			dptr += strlen("sip:");
261ac367740SPatrick McHardy 			break;
262ac367740SPatrick McHardy 		}
26354101f4fSPatrick McHardy 	}
264624f8b7bSPatrick McHardy 	if (!skp_epaddr_len(ct, dptr, limit, &shift))
265ac367740SPatrick McHardy 		return 0;
266624f8b7bSPatrick McHardy 	dptr += shift;
267624f8b7bSPatrick McHardy 
26802b69cbdSPatrick McHardy 	if (!sip_parse_addr(ct, dptr, &end, addr, limit, true))
269624f8b7bSPatrick McHardy 		return -1;
270624f8b7bSPatrick McHardy 	if (end < limit && *end == ':') {
271624f8b7bSPatrick McHardy 		end++;
272624f8b7bSPatrick McHardy 		p = simple_strtoul(end, (char **)&end, 10);
273624f8b7bSPatrick McHardy 		if (p < 1024 || p > 65535)
274624f8b7bSPatrick McHardy 			return -1;
275624f8b7bSPatrick McHardy 		*port = htons(p);
276624f8b7bSPatrick McHardy 	} else
277624f8b7bSPatrick McHardy 		*port = htons(SIP_PORT);
278624f8b7bSPatrick McHardy 
279624f8b7bSPatrick McHardy 	if (end == dptr)
280624f8b7bSPatrick McHardy 		return 0;
281624f8b7bSPatrick McHardy 	*matchoff = dptr - start;
282624f8b7bSPatrick McHardy 	*matchlen = end - dptr;
283ac367740SPatrick McHardy 	return 1;
284ac367740SPatrick McHardy }
285ac367740SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_parse_request);
286ac367740SPatrick McHardy 
287ea45f12aSPatrick McHardy /* SIP header parsing: SIP headers are located at the beginning of a line, but
288ea45f12aSPatrick McHardy  * may span several lines, in which case the continuation lines begin with a
289ea45f12aSPatrick McHardy  * whitespace character. RFC 2543 allows lines to be terminated with CR, LF or
290ea45f12aSPatrick McHardy  * CRLF, RFC 3261 allows only CRLF, we support both.
291ea45f12aSPatrick McHardy  *
292ea45f12aSPatrick McHardy  * Headers are followed by (optionally) whitespace, a colon, again (optionally)
293ea45f12aSPatrick McHardy  * whitespace and the values. Whitespace in this context means any amount of
294ea45f12aSPatrick McHardy  * tabs, spaces and continuation lines, which are treated as a single whitespace
295ea45f12aSPatrick McHardy  * character.
29605e3ced2SPatrick McHardy  *
2973ad2f3fbSDaniel Mack  * Some headers may appear multiple times. A comma separated list of values is
29805e3ced2SPatrick McHardy  * equivalent to multiple headers.
299ea45f12aSPatrick McHardy  */
300ea45f12aSPatrick McHardy static const struct sip_header ct_sip_hdrs[] = {
30130f33e6dSPatrick McHardy 	[SIP_HDR_CSEQ]			= SIP_HDR("CSeq", NULL, NULL, digits_len),
302ea45f12aSPatrick McHardy 	[SIP_HDR_FROM]			= SIP_HDR("From", "f", "sip:", skp_epaddr_len),
303ea45f12aSPatrick McHardy 	[SIP_HDR_TO]			= SIP_HDR("To", "t", "sip:", skp_epaddr_len),
304ea45f12aSPatrick McHardy 	[SIP_HDR_CONTACT]		= SIP_HDR("Contact", "m", "sip:", skp_epaddr_len),
305f5b321bdSPatrick McHardy 	[SIP_HDR_VIA_UDP]		= SIP_HDR("Via", "v", "UDP ", epaddr_len),
306f5b321bdSPatrick McHardy 	[SIP_HDR_VIA_TCP]		= SIP_HDR("Via", "v", "TCP ", epaddr_len),
3070f32a40fSPatrick McHardy 	[SIP_HDR_EXPIRES]		= SIP_HDR("Expires", NULL, NULL, digits_len),
308ea45f12aSPatrick McHardy 	[SIP_HDR_CONTENT_LENGTH]	= SIP_HDR("Content-Length", "l", NULL, digits_len),
309001985b2SSimon Horman 	[SIP_HDR_CALL_ID]		= SIP_HDR("Call-Id", "i", NULL, callid_len),
310ea45f12aSPatrick McHardy };
311ea45f12aSPatrick McHardy 
sip_follow_continuation(const char * dptr,const char * limit)312ea45f12aSPatrick McHardy static const char *sip_follow_continuation(const char *dptr, const char *limit)
3139fafcd7bSPatrick McHardy {
314ea45f12aSPatrick McHardy 	/* Walk past newline */
315ea45f12aSPatrick McHardy 	if (++dptr >= limit)
316ea45f12aSPatrick McHardy 		return NULL;
3179fafcd7bSPatrick McHardy 
318ea45f12aSPatrick McHardy 	/* Skip '\n' in CR LF */
319ea45f12aSPatrick McHardy 	if (*(dptr - 1) == '\r' && *dptr == '\n') {
320ea45f12aSPatrick McHardy 		if (++dptr >= limit)
321ea45f12aSPatrick McHardy 			return NULL;
322ea45f12aSPatrick McHardy 	}
3239fafcd7bSPatrick McHardy 
324ea45f12aSPatrick McHardy 	/* Continuation line? */
325ea45f12aSPatrick McHardy 	if (*dptr != ' ' && *dptr != '\t')
326ea45f12aSPatrick McHardy 		return NULL;
327ea45f12aSPatrick McHardy 
328ea45f12aSPatrick McHardy 	/* skip leading whitespace */
329ea45f12aSPatrick McHardy 	for (; dptr < limit; dptr++) {
330ea45f12aSPatrick McHardy 		if (*dptr != ' ' && *dptr != '\t')
331ea45f12aSPatrick McHardy 			break;
332ea45f12aSPatrick McHardy 	}
333ea45f12aSPatrick McHardy 	return dptr;
334ea45f12aSPatrick McHardy }
335ea45f12aSPatrick McHardy 
sip_skip_whitespace(const char * dptr,const char * limit)336ea45f12aSPatrick McHardy static const char *sip_skip_whitespace(const char *dptr, const char *limit)
337ea45f12aSPatrick McHardy {
338ea45f12aSPatrick McHardy 	for (; dptr < limit; dptr++) {
3391bcabc81SMarco Angaroni 		if (*dptr == ' ' || *dptr == '\t')
340ea45f12aSPatrick McHardy 			continue;
341ea45f12aSPatrick McHardy 		if (*dptr != '\r' && *dptr != '\n')
342ea45f12aSPatrick McHardy 			break;
343ea45f12aSPatrick McHardy 		dptr = sip_follow_continuation(dptr, limit);
34468cb9fe4SMarco Angaroni 		break;
345ea45f12aSPatrick McHardy 	}
346ea45f12aSPatrick McHardy 	return dptr;
347ea45f12aSPatrick McHardy }
348ea45f12aSPatrick McHardy 
349ea45f12aSPatrick McHardy /* Search within a SIP header value, dealing with continuation lines */
ct_sip_header_search(const char * dptr,const char * limit,const char * needle,unsigned int len)350ea45f12aSPatrick McHardy static const char *ct_sip_header_search(const char *dptr, const char *limit,
351ea45f12aSPatrick McHardy 					const char *needle, unsigned int len)
352ea45f12aSPatrick McHardy {
353ea45f12aSPatrick McHardy 	for (limit -= len; dptr < limit; dptr++) {
354ea45f12aSPatrick McHardy 		if (*dptr == '\r' || *dptr == '\n') {
355ea45f12aSPatrick McHardy 			dptr = sip_follow_continuation(dptr, limit);
356ea45f12aSPatrick McHardy 			if (dptr == NULL)
357ea45f12aSPatrick McHardy 				break;
3589fafcd7bSPatrick McHardy 			continue;
3599fafcd7bSPatrick McHardy 		}
3609fafcd7bSPatrick McHardy 
36118082746SRasmus Villemoes 		if (strncasecmp(dptr, needle, len) == 0)
362ea45f12aSPatrick McHardy 			return dptr;
363ea45f12aSPatrick McHardy 	}
364ea45f12aSPatrick McHardy 	return NULL;
365ea45f12aSPatrick McHardy }
366ea45f12aSPatrick McHardy 
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)367ea45f12aSPatrick McHardy int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
368ea45f12aSPatrick McHardy 		      unsigned int dataoff, unsigned int datalen,
369ea45f12aSPatrick McHardy 		      enum sip_header_types type,
370ea45f12aSPatrick McHardy 		      unsigned int *matchoff, unsigned int *matchlen)
371ea45f12aSPatrick McHardy {
372ea45f12aSPatrick McHardy 	const struct sip_header *hdr = &ct_sip_hdrs[type];
373ea45f12aSPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen;
374ea45f12aSPatrick McHardy 	int shift = 0;
375ea45f12aSPatrick McHardy 
376ea45f12aSPatrick McHardy 	for (dptr += dataoff; dptr < limit; dptr++) {
377ea45f12aSPatrick McHardy 		/* Find beginning of line */
378ea45f12aSPatrick McHardy 		if (*dptr != '\r' && *dptr != '\n')
379ea45f12aSPatrick McHardy 			continue;
380ea45f12aSPatrick McHardy 		if (++dptr >= limit)
381ea45f12aSPatrick McHardy 			break;
382ea45f12aSPatrick McHardy 		if (*(dptr - 1) == '\r' && *dptr == '\n') {
383ea45f12aSPatrick McHardy 			if (++dptr >= limit)
384ea45f12aSPatrick McHardy 				break;
385ea45f12aSPatrick McHardy 		}
386ea45f12aSPatrick McHardy 
387ea45f12aSPatrick McHardy 		/* Skip continuation lines */
388ea45f12aSPatrick McHardy 		if (*dptr == ' ' || *dptr == '\t')
389ea45f12aSPatrick McHardy 			continue;
390ea45f12aSPatrick McHardy 
391ea45f12aSPatrick McHardy 		/* Find header. Compact headers must be followed by a
392ea45f12aSPatrick McHardy 		 * non-alphabetic character to avoid mismatches. */
393ea45f12aSPatrick McHardy 		if (limit - dptr >= hdr->len &&
39418082746SRasmus Villemoes 		    strncasecmp(dptr, hdr->name, hdr->len) == 0)
395ea45f12aSPatrick McHardy 			dptr += hdr->len;
396ea45f12aSPatrick McHardy 		else if (hdr->cname && limit - dptr >= hdr->clen + 1 &&
39718082746SRasmus Villemoes 			 strncasecmp(dptr, hdr->cname, hdr->clen) == 0 &&
398135d0189SPatrick McHardy 			 !isalpha(*(dptr + hdr->clen)))
399ea45f12aSPatrick McHardy 			dptr += hdr->clen;
400ea45f12aSPatrick McHardy 		else
401ea45f12aSPatrick McHardy 			continue;
402ea45f12aSPatrick McHardy 
403ea45f12aSPatrick McHardy 		/* Find and skip colon */
404ea45f12aSPatrick McHardy 		dptr = sip_skip_whitespace(dptr, limit);
405ea45f12aSPatrick McHardy 		if (dptr == NULL)
406ea45f12aSPatrick McHardy 			break;
407ea45f12aSPatrick McHardy 		if (*dptr != ':' || ++dptr >= limit)
408ea45f12aSPatrick McHardy 			break;
409ea45f12aSPatrick McHardy 
410ea45f12aSPatrick McHardy 		/* Skip whitespace after colon */
411ea45f12aSPatrick McHardy 		dptr = sip_skip_whitespace(dptr, limit);
412ea45f12aSPatrick McHardy 		if (dptr == NULL)
413ea45f12aSPatrick McHardy 			break;
414ea45f12aSPatrick McHardy 
415ea45f12aSPatrick McHardy 		*matchoff = dptr - start;
416ea45f12aSPatrick McHardy 		if (hdr->search) {
417ea45f12aSPatrick McHardy 			dptr = ct_sip_header_search(dptr, limit, hdr->search,
418ea45f12aSPatrick McHardy 						    hdr->slen);
419ea45f12aSPatrick McHardy 			if (!dptr)
420ea45f12aSPatrick McHardy 				return -1;
421ea45f12aSPatrick McHardy 			dptr += hdr->slen;
422ea45f12aSPatrick McHardy 		}
423ea45f12aSPatrick McHardy 
424ea45f12aSPatrick McHardy 		*matchlen = hdr->match_len(ct, dptr, limit, &shift);
4259fafcd7bSPatrick McHardy 		if (!*matchlen)
4269fafcd7bSPatrick McHardy 			return -1;
427ea45f12aSPatrick McHardy 		*matchoff = dptr - start + shift;
4289fafcd7bSPatrick McHardy 		return 1;
4299fafcd7bSPatrick McHardy 	}
4309fafcd7bSPatrick McHardy 	return 0;
4319fafcd7bSPatrick McHardy }
432ea45f12aSPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_get_header);
4339fafcd7bSPatrick McHardy 
4343ad2f3fbSDaniel Mack /* Get next header field in a list of comma separated values */
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)43505e3ced2SPatrick McHardy static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr,
43605e3ced2SPatrick McHardy 			      unsigned int dataoff, unsigned int datalen,
43705e3ced2SPatrick McHardy 			      enum sip_header_types type,
43805e3ced2SPatrick McHardy 			      unsigned int *matchoff, unsigned int *matchlen)
43905e3ced2SPatrick McHardy {
44005e3ced2SPatrick McHardy 	const struct sip_header *hdr = &ct_sip_hdrs[type];
44105e3ced2SPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen;
44205e3ced2SPatrick McHardy 	int shift = 0;
44305e3ced2SPatrick McHardy 
44405e3ced2SPatrick McHardy 	dptr += dataoff;
44505e3ced2SPatrick McHardy 
44605e3ced2SPatrick McHardy 	dptr = ct_sip_header_search(dptr, limit, ",", strlen(","));
44705e3ced2SPatrick McHardy 	if (!dptr)
44805e3ced2SPatrick McHardy 		return 0;
44905e3ced2SPatrick McHardy 
45005e3ced2SPatrick McHardy 	dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen);
45105e3ced2SPatrick McHardy 	if (!dptr)
45205e3ced2SPatrick McHardy 		return 0;
45305e3ced2SPatrick McHardy 	dptr += hdr->slen;
45405e3ced2SPatrick McHardy 
45505e3ced2SPatrick McHardy 	*matchoff = dptr - start;
45605e3ced2SPatrick McHardy 	*matchlen = hdr->match_len(ct, dptr, limit, &shift);
45705e3ced2SPatrick McHardy 	if (!*matchlen)
45805e3ced2SPatrick McHardy 		return -1;
45905e3ced2SPatrick McHardy 	*matchoff += shift;
46005e3ced2SPatrick McHardy 	return 1;
46105e3ced2SPatrick McHardy }
46205e3ced2SPatrick McHardy 
46305e3ced2SPatrick McHardy /* Walk through headers until a parsable one is found or no header of the
46405e3ced2SPatrick McHardy  * given type is left. */
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)46505e3ced2SPatrick McHardy static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr,
46605e3ced2SPatrick McHardy 			       unsigned int dataoff, unsigned int datalen,
46705e3ced2SPatrick McHardy 			       enum sip_header_types type, int *in_header,
46805e3ced2SPatrick McHardy 			       unsigned int *matchoff, unsigned int *matchlen)
46905e3ced2SPatrick McHardy {
47005e3ced2SPatrick McHardy 	int ret;
47105e3ced2SPatrick McHardy 
47205e3ced2SPatrick McHardy 	if (in_header && *in_header) {
47305e3ced2SPatrick McHardy 		while (1) {
47405e3ced2SPatrick McHardy 			ret = ct_sip_next_header(ct, dptr, dataoff, datalen,
47505e3ced2SPatrick McHardy 						 type, matchoff, matchlen);
47605e3ced2SPatrick McHardy 			if (ret > 0)
47705e3ced2SPatrick McHardy 				return ret;
47805e3ced2SPatrick McHardy 			if (ret == 0)
47905e3ced2SPatrick McHardy 				break;
48039aebedeSIgor Ryzhov 			dataoff = *matchoff;
48105e3ced2SPatrick McHardy 		}
48205e3ced2SPatrick McHardy 		*in_header = 0;
48305e3ced2SPatrick McHardy 	}
48405e3ced2SPatrick McHardy 
48505e3ced2SPatrick McHardy 	while (1) {
48605e3ced2SPatrick McHardy 		ret = ct_sip_get_header(ct, dptr, dataoff, datalen,
48705e3ced2SPatrick McHardy 					type, matchoff, matchlen);
48805e3ced2SPatrick McHardy 		if (ret > 0)
48905e3ced2SPatrick McHardy 			break;
49005e3ced2SPatrick McHardy 		if (ret == 0)
49105e3ced2SPatrick McHardy 			return ret;
49239aebedeSIgor Ryzhov 		dataoff = *matchoff;
49305e3ced2SPatrick McHardy 	}
49405e3ced2SPatrick McHardy 
49505e3ced2SPatrick McHardy 	if (in_header)
49605e3ced2SPatrick McHardy 		*in_header = 1;
49705e3ced2SPatrick McHardy 	return 1;
49805e3ced2SPatrick McHardy }
49905e3ced2SPatrick McHardy 
50005e3ced2SPatrick McHardy /* Locate a SIP header, parse the URI and return the offset and length of
50105e3ced2SPatrick McHardy  * the address as well as the address and port themselves. A stream of
50205e3ced2SPatrick McHardy  * headers can be parsed by handing in a non-NULL datalen and in_header
50305e3ced2SPatrick McHardy  * pointer.
50405e3ced2SPatrick McHardy  */
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)50505e3ced2SPatrick McHardy int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr,
50605e3ced2SPatrick McHardy 			    unsigned int *dataoff, unsigned int datalen,
50705e3ced2SPatrick McHardy 			    enum sip_header_types type, int *in_header,
50805e3ced2SPatrick McHardy 			    unsigned int *matchoff, unsigned int *matchlen,
50905e3ced2SPatrick McHardy 			    union nf_inet_addr *addr, __be16 *port)
51005e3ced2SPatrick McHardy {
51105e3ced2SPatrick McHardy 	const char *c, *limit = dptr + datalen;
51205e3ced2SPatrick McHardy 	unsigned int p;
51305e3ced2SPatrick McHardy 	int ret;
51405e3ced2SPatrick McHardy 
51505e3ced2SPatrick McHardy 	ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen,
51605e3ced2SPatrick McHardy 				  type, in_header, matchoff, matchlen);
51705e3ced2SPatrick McHardy 	WARN_ON(ret < 0);
51805e3ced2SPatrick McHardy 	if (ret == 0)
51905e3ced2SPatrick McHardy 		return ret;
52005e3ced2SPatrick McHardy 
52102b69cbdSPatrick McHardy 	if (!sip_parse_addr(ct, dptr + *matchoff, &c, addr, limit, true))
52205e3ced2SPatrick McHardy 		return -1;
52305e3ced2SPatrick McHardy 	if (*c == ':') {
52405e3ced2SPatrick McHardy 		c++;
52505e3ced2SPatrick McHardy 		p = simple_strtoul(c, (char **)&c, 10);
52605e3ced2SPatrick McHardy 		if (p < 1024 || p > 65535)
52705e3ced2SPatrick McHardy 			return -1;
52805e3ced2SPatrick McHardy 		*port = htons(p);
52905e3ced2SPatrick McHardy 	} else
53005e3ced2SPatrick McHardy 		*port = htons(SIP_PORT);
53105e3ced2SPatrick McHardy 
53205e3ced2SPatrick McHardy 	if (dataoff)
53305e3ced2SPatrick McHardy 		*dataoff = c - dptr;
53405e3ced2SPatrick McHardy 	return 1;
53505e3ced2SPatrick McHardy }
53605e3ced2SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri);
53705e3ced2SPatrick McHardy 
ct_sip_parse_param(const struct nf_conn * ct,const char * dptr,unsigned int dataoff,unsigned int datalen,const char * name,unsigned int * matchoff,unsigned int * matchlen)538f5b321bdSPatrick McHardy static int ct_sip_parse_param(const struct nf_conn *ct, const char *dptr,
539f5b321bdSPatrick McHardy 			      unsigned int dataoff, unsigned int datalen,
540f5b321bdSPatrick McHardy 			      const char *name,
541f5b321bdSPatrick McHardy 			      unsigned int *matchoff, unsigned int *matchlen)
542f5b321bdSPatrick McHardy {
543f5b321bdSPatrick McHardy 	const char *limit = dptr + datalen;
544f5b321bdSPatrick McHardy 	const char *start;
545f5b321bdSPatrick McHardy 	const char *end;
546f5b321bdSPatrick McHardy 
547f5b321bdSPatrick McHardy 	limit = ct_sip_header_search(dptr + dataoff, limit, ",", strlen(","));
548f5b321bdSPatrick McHardy 	if (!limit)
549f5b321bdSPatrick McHardy 		limit = dptr + datalen;
550f5b321bdSPatrick McHardy 
551f5b321bdSPatrick McHardy 	start = ct_sip_header_search(dptr + dataoff, limit, name, strlen(name));
552f5b321bdSPatrick McHardy 	if (!start)
553f5b321bdSPatrick McHardy 		return 0;
554f5b321bdSPatrick McHardy 	start += strlen(name);
555f5b321bdSPatrick McHardy 
556f5b321bdSPatrick McHardy 	end = ct_sip_header_search(start, limit, ";", strlen(";"));
557f5b321bdSPatrick McHardy 	if (!end)
558f5b321bdSPatrick McHardy 		end = limit;
559f5b321bdSPatrick McHardy 
560f5b321bdSPatrick McHardy 	*matchoff = start - dptr;
561f5b321bdSPatrick McHardy 	*matchlen = end - start;
562f5b321bdSPatrick McHardy 	return 1;
563f5b321bdSPatrick McHardy }
564f5b321bdSPatrick McHardy 
5652bbb2116SPatrick McHardy /* Parse address from header parameter and return address, offset and length */
ct_sip_parse_address_param(const struct nf_conn * ct,const char * dptr,unsigned int dataoff,unsigned int datalen,const char * name,unsigned int * matchoff,unsigned int * matchlen,union nf_inet_addr * addr,bool delim)5662bbb2116SPatrick McHardy int ct_sip_parse_address_param(const struct nf_conn *ct, const char *dptr,
5672bbb2116SPatrick McHardy 			       unsigned int dataoff, unsigned int datalen,
5682bbb2116SPatrick McHardy 			       const char *name,
5692bbb2116SPatrick McHardy 			       unsigned int *matchoff, unsigned int *matchlen,
57002b69cbdSPatrick McHardy 			       union nf_inet_addr *addr, bool delim)
5712bbb2116SPatrick McHardy {
5722bbb2116SPatrick McHardy 	const char *limit = dptr + datalen;
5732bbb2116SPatrick McHardy 	const char *start, *end;
5742bbb2116SPatrick McHardy 
5752bbb2116SPatrick McHardy 	limit = ct_sip_header_search(dptr + dataoff, limit, ",", strlen(","));
5762bbb2116SPatrick McHardy 	if (!limit)
5772bbb2116SPatrick McHardy 		limit = dptr + datalen;
5782bbb2116SPatrick McHardy 
5792bbb2116SPatrick McHardy 	start = ct_sip_header_search(dptr + dataoff, limit, name, strlen(name));
5802bbb2116SPatrick McHardy 	if (!start)
5812bbb2116SPatrick McHardy 		return 0;
5822bbb2116SPatrick McHardy 
5832bbb2116SPatrick McHardy 	start += strlen(name);
58402b69cbdSPatrick McHardy 	if (!sip_parse_addr(ct, start, &end, addr, limit, delim))
5852bbb2116SPatrick McHardy 		return 0;
5862bbb2116SPatrick McHardy 	*matchoff = start - dptr;
5872bbb2116SPatrick McHardy 	*matchlen = end - start;
5882bbb2116SPatrick McHardy 	return 1;
5892bbb2116SPatrick McHardy }
5902bbb2116SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_parse_address_param);
5912bbb2116SPatrick McHardy 
5922bbb2116SPatrick McHardy /* Parse numerical header parameter and return value, offset and length */
ct_sip_parse_numerical_param(const struct nf_conn * ct,const char * dptr,unsigned int dataoff,unsigned int datalen,const char * name,unsigned int * matchoff,unsigned int * matchlen,unsigned int * val)5932bbb2116SPatrick McHardy int ct_sip_parse_numerical_param(const struct nf_conn *ct, const char *dptr,
5942bbb2116SPatrick McHardy 				 unsigned int dataoff, unsigned int datalen,
5952bbb2116SPatrick McHardy 				 const char *name,
5962bbb2116SPatrick McHardy 				 unsigned int *matchoff, unsigned int *matchlen,
5972bbb2116SPatrick McHardy 				 unsigned int *val)
5982bbb2116SPatrick McHardy {
5992bbb2116SPatrick McHardy 	const char *limit = dptr + datalen;
6002bbb2116SPatrick McHardy 	const char *start;
6012bbb2116SPatrick McHardy 	char *end;
6022bbb2116SPatrick McHardy 
6032bbb2116SPatrick McHardy 	limit = ct_sip_header_search(dptr + dataoff, limit, ",", strlen(","));
6042bbb2116SPatrick McHardy 	if (!limit)
6052bbb2116SPatrick McHardy 		limit = dptr + datalen;
6062bbb2116SPatrick McHardy 
6072bbb2116SPatrick McHardy 	start = ct_sip_header_search(dptr + dataoff, limit, name, strlen(name));
6082bbb2116SPatrick McHardy 	if (!start)
6092bbb2116SPatrick McHardy 		return 0;
6102bbb2116SPatrick McHardy 
6112bbb2116SPatrick McHardy 	start += strlen(name);
6122bbb2116SPatrick McHardy 	*val = simple_strtoul(start, &end, 0);
6132bbb2116SPatrick McHardy 	if (start == end)
614*f188d300SIlia.Gavrilov 		return -1;
6152bbb2116SPatrick McHardy 	if (matchoff && matchlen) {
6162bbb2116SPatrick McHardy 		*matchoff = start - dptr;
6172bbb2116SPatrick McHardy 		*matchlen = end - start;
6182bbb2116SPatrick McHardy 	}
6192bbb2116SPatrick McHardy 	return 1;
6202bbb2116SPatrick McHardy }
6212bbb2116SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_parse_numerical_param);
6222bbb2116SPatrick McHardy 
ct_sip_parse_transport(struct nf_conn * ct,const char * dptr,unsigned int dataoff,unsigned int datalen,u8 * proto)623f5b321bdSPatrick McHardy static int ct_sip_parse_transport(struct nf_conn *ct, const char *dptr,
624f5b321bdSPatrick McHardy 				  unsigned int dataoff, unsigned int datalen,
625f5b321bdSPatrick McHardy 				  u8 *proto)
626f5b321bdSPatrick McHardy {
627f5b321bdSPatrick McHardy 	unsigned int matchoff, matchlen;
628f5b321bdSPatrick McHardy 
629f5b321bdSPatrick McHardy 	if (ct_sip_parse_param(ct, dptr, dataoff, datalen, "transport=",
630f5b321bdSPatrick McHardy 			       &matchoff, &matchlen)) {
63118082746SRasmus Villemoes 		if (!strncasecmp(dptr + matchoff, "TCP", strlen("TCP")))
632f5b321bdSPatrick McHardy 			*proto = IPPROTO_TCP;
63318082746SRasmus Villemoes 		else if (!strncasecmp(dptr + matchoff, "UDP", strlen("UDP")))
634f5b321bdSPatrick McHardy 			*proto = IPPROTO_UDP;
635f5b321bdSPatrick McHardy 		else
636f5b321bdSPatrick McHardy 			return 0;
637f5b321bdSPatrick McHardy 
638f5b321bdSPatrick McHardy 		if (*proto != nf_ct_protonum(ct))
639f5b321bdSPatrick McHardy 			return 0;
640f5b321bdSPatrick McHardy 	} else
641f5b321bdSPatrick McHardy 		*proto = nf_ct_protonum(ct);
642f5b321bdSPatrick McHardy 
643f5b321bdSPatrick McHardy 	return 1;
644f5b321bdSPatrick McHardy }
645f5b321bdSPatrick McHardy 
sdp_parse_addr(const struct nf_conn * ct,const char * cp,const char ** endp,union nf_inet_addr * addr,const char * limit)64602b69cbdSPatrick McHardy static int sdp_parse_addr(const struct nf_conn *ct, const char *cp,
64702b69cbdSPatrick McHardy 			  const char **endp, union nf_inet_addr *addr,
64802b69cbdSPatrick McHardy 			  const char *limit)
64902b69cbdSPatrick McHardy {
65002b69cbdSPatrick McHardy 	const char *end;
65102b69cbdSPatrick McHardy 	int ret;
65202b69cbdSPatrick McHardy 
65302b69cbdSPatrick McHardy 	memset(addr, 0, sizeof(*addr));
65402b69cbdSPatrick McHardy 	switch (nf_ct_l3num(ct)) {
65502b69cbdSPatrick McHardy 	case AF_INET:
65602b69cbdSPatrick McHardy 		ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
65702b69cbdSPatrick McHardy 		break;
65802b69cbdSPatrick McHardy 	case AF_INET6:
65902b69cbdSPatrick McHardy 		ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
66002b69cbdSPatrick McHardy 		break;
66102b69cbdSPatrick McHardy 	default:
66202b69cbdSPatrick McHardy 		BUG();
66302b69cbdSPatrick McHardy 	}
66402b69cbdSPatrick McHardy 
66502b69cbdSPatrick McHardy 	if (ret == 0)
66602b69cbdSPatrick McHardy 		return 0;
66702b69cbdSPatrick McHardy 	if (endp)
66802b69cbdSPatrick McHardy 		*endp = end;
66902b69cbdSPatrick McHardy 	return 1;
67002b69cbdSPatrick McHardy }
67102b69cbdSPatrick McHardy 
67202b69cbdSPatrick McHardy /* skip ip address. returns its length. */
sdp_addr_len(const struct nf_conn * ct,const char * dptr,const char * limit,int * shift)67302b69cbdSPatrick McHardy static int sdp_addr_len(const struct nf_conn *ct, const char *dptr,
67402b69cbdSPatrick McHardy 			const char *limit, int *shift)
67502b69cbdSPatrick McHardy {
67602b69cbdSPatrick McHardy 	union nf_inet_addr addr;
67702b69cbdSPatrick McHardy 	const char *aux = dptr;
67802b69cbdSPatrick McHardy 
67902b69cbdSPatrick McHardy 	if (!sdp_parse_addr(ct, dptr, &dptr, &addr, limit)) {
68002b69cbdSPatrick McHardy 		pr_debug("ip: %s parse failed.!\n", dptr);
68102b69cbdSPatrick McHardy 		return 0;
68202b69cbdSPatrick McHardy 	}
68302b69cbdSPatrick McHardy 
68402b69cbdSPatrick McHardy 	return dptr - aux;
68502b69cbdSPatrick McHardy }
68602b69cbdSPatrick McHardy 
6873e9b4600SPatrick McHardy /* SDP header parsing: a SDP session description contains an ordered set of
6883e9b4600SPatrick McHardy  * headers, starting with a section containing general session parameters,
6893e9b4600SPatrick McHardy  * optionally followed by multiple media descriptions.
6903e9b4600SPatrick McHardy  *
6913e9b4600SPatrick McHardy  * SDP headers always start at the beginning of a line. According to RFC 2327:
6923e9b4600SPatrick McHardy  * "The sequence CRLF (0x0d0a) is used to end a record, although parsers should
6933e9b4600SPatrick McHardy  * be tolerant and also accept records terminated with a single newline
6943e9b4600SPatrick McHardy  * character". We handle both cases.
6953e9b4600SPatrick McHardy  */
6969a664821SPatrick McHardy static const struct sip_header ct_sdp_hdrs_v4[] = {
6973e9b4600SPatrick McHardy 	[SDP_HDR_VERSION]	= SDP_HDR("v=", NULL, digits_len),
6989a664821SPatrick McHardy 	[SDP_HDR_OWNER]		= SDP_HDR("o=", "IN IP4 ", sdp_addr_len),
6999a664821SPatrick McHardy 	[SDP_HDR_CONNECTION]	= SDP_HDR("c=", "IN IP4 ", sdp_addr_len),
7009a664821SPatrick McHardy 	[SDP_HDR_MEDIA]		= SDP_HDR("m=", NULL, media_len),
7019a664821SPatrick McHardy };
7029a664821SPatrick McHardy 
7039a664821SPatrick McHardy static const struct sip_header ct_sdp_hdrs_v6[] = {
7049a664821SPatrick McHardy 	[SDP_HDR_VERSION]	= SDP_HDR("v=", NULL, digits_len),
7059a664821SPatrick McHardy 	[SDP_HDR_OWNER]		= SDP_HDR("o=", "IN IP6 ", sdp_addr_len),
7069a664821SPatrick McHardy 	[SDP_HDR_CONNECTION]	= SDP_HDR("c=", "IN IP6 ", sdp_addr_len),
7070d0ab037SPatrick McHardy 	[SDP_HDR_MEDIA]		= SDP_HDR("m=", NULL, media_len),
7083e9b4600SPatrick McHardy };
7093e9b4600SPatrick McHardy 
7103e9b4600SPatrick McHardy /* Linear string search within SDP header values */
ct_sdp_header_search(const char * dptr,const char * limit,const char * needle,unsigned int len)7113e9b4600SPatrick McHardy static const char *ct_sdp_header_search(const char *dptr, const char *limit,
7123e9b4600SPatrick McHardy 					const char *needle, unsigned int len)
7133e9b4600SPatrick McHardy {
7143e9b4600SPatrick McHardy 	for (limit -= len; dptr < limit; dptr++) {
7153e9b4600SPatrick McHardy 		if (*dptr == '\r' || *dptr == '\n')
7163e9b4600SPatrick McHardy 			break;
7173e9b4600SPatrick McHardy 		if (strncmp(dptr, needle, len) == 0)
7183e9b4600SPatrick McHardy 			return dptr;
7193e9b4600SPatrick McHardy 	}
7203e9b4600SPatrick McHardy 	return NULL;
7213e9b4600SPatrick McHardy }
7223e9b4600SPatrick McHardy 
7233e9b4600SPatrick McHardy /* Locate a SDP header (optionally a substring within the header value),
72425985edcSLucas De Marchi  * optionally stopping at the first occurrence of the term header, parse
7253e9b4600SPatrick McHardy  * it and return the offset and length of the data we're interested in.
7263e9b4600SPatrick McHardy  */
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)7273e9b4600SPatrick McHardy int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
7283e9b4600SPatrick McHardy 			  unsigned int dataoff, unsigned int datalen,
7293e9b4600SPatrick McHardy 			  enum sdp_header_types type,
7303e9b4600SPatrick McHardy 			  enum sdp_header_types term,
7313e9b4600SPatrick McHardy 			  unsigned int *matchoff, unsigned int *matchlen)
7323e9b4600SPatrick McHardy {
7339a664821SPatrick McHardy 	const struct sip_header *hdrs, *hdr, *thdr;
7343e9b4600SPatrick McHardy 	const char *start = dptr, *limit = dptr + datalen;
7353e9b4600SPatrick McHardy 	int shift = 0;
7363e9b4600SPatrick McHardy 
7379a664821SPatrick McHardy 	hdrs = nf_ct_l3num(ct) == NFPROTO_IPV4 ? ct_sdp_hdrs_v4 : ct_sdp_hdrs_v6;
7389a664821SPatrick McHardy 	hdr = &hdrs[type];
7399a664821SPatrick McHardy 	thdr = &hdrs[term];
7409a664821SPatrick McHardy 
7413e9b4600SPatrick McHardy 	for (dptr += dataoff; dptr < limit; dptr++) {
7423e9b4600SPatrick McHardy 		/* Find beginning of line */
7433e9b4600SPatrick McHardy 		if (*dptr != '\r' && *dptr != '\n')
7443e9b4600SPatrick McHardy 			continue;
7453e9b4600SPatrick McHardy 		if (++dptr >= limit)
7463e9b4600SPatrick McHardy 			break;
7473e9b4600SPatrick McHardy 		if (*(dptr - 1) == '\r' && *dptr == '\n') {
7483e9b4600SPatrick McHardy 			if (++dptr >= limit)
7493e9b4600SPatrick McHardy 				break;
7503e9b4600SPatrick McHardy 		}
7513e9b4600SPatrick McHardy 
7523e9b4600SPatrick McHardy 		if (term != SDP_HDR_UNSPEC &&
7533e9b4600SPatrick McHardy 		    limit - dptr >= thdr->len &&
75418082746SRasmus Villemoes 		    strncasecmp(dptr, thdr->name, thdr->len) == 0)
7553e9b4600SPatrick McHardy 			break;
7563e9b4600SPatrick McHardy 		else if (limit - dptr >= hdr->len &&
75718082746SRasmus Villemoes 			 strncasecmp(dptr, hdr->name, hdr->len) == 0)
7583e9b4600SPatrick McHardy 			dptr += hdr->len;
7593e9b4600SPatrick McHardy 		else
7603e9b4600SPatrick McHardy 			continue;
7613e9b4600SPatrick McHardy 
7623e9b4600SPatrick McHardy 		*matchoff = dptr - start;
7633e9b4600SPatrick McHardy 		if (hdr->search) {
7643e9b4600SPatrick McHardy 			dptr = ct_sdp_header_search(dptr, limit, hdr->search,
7653e9b4600SPatrick McHardy 						    hdr->slen);
7663e9b4600SPatrick McHardy 			if (!dptr)
7673e9b4600SPatrick McHardy 				return -1;
7683e9b4600SPatrick McHardy 			dptr += hdr->slen;
7693e9b4600SPatrick McHardy 		}
7703e9b4600SPatrick McHardy 
7713e9b4600SPatrick McHardy 		*matchlen = hdr->match_len(ct, dptr, limit, &shift);
7723e9b4600SPatrick McHardy 		if (!*matchlen)
7733e9b4600SPatrick McHardy 			return -1;
7743e9b4600SPatrick McHardy 		*matchoff = dptr - start + shift;
7753e9b4600SPatrick McHardy 		return 1;
7763e9b4600SPatrick McHardy 	}
7773e9b4600SPatrick McHardy 	return 0;
7783e9b4600SPatrick McHardy }
7793e9b4600SPatrick McHardy EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header);
7803e9b4600SPatrick McHardy 
ct_sip_parse_sdp_addr(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,union nf_inet_addr * addr)7814ab9e64eSPatrick McHardy static int ct_sip_parse_sdp_addr(const struct nf_conn *ct, const char *dptr,
7824ab9e64eSPatrick McHardy 				 unsigned int dataoff, unsigned int datalen,
7834ab9e64eSPatrick McHardy 				 enum sdp_header_types type,
7844ab9e64eSPatrick McHardy 				 enum sdp_header_types term,
7854ab9e64eSPatrick McHardy 				 unsigned int *matchoff, unsigned int *matchlen,
7864ab9e64eSPatrick McHardy 				 union nf_inet_addr *addr)
7874ab9e64eSPatrick McHardy {
7884ab9e64eSPatrick McHardy 	int ret;
7894ab9e64eSPatrick McHardy 
7904ab9e64eSPatrick McHardy 	ret = ct_sip_get_sdp_header(ct, dptr, dataoff, datalen, type, term,
7914ab9e64eSPatrick McHardy 				    matchoff, matchlen);
7924ab9e64eSPatrick McHardy 	if (ret <= 0)
7934ab9e64eSPatrick McHardy 		return ret;
7944ab9e64eSPatrick McHardy 
79502b69cbdSPatrick McHardy 	if (!sdp_parse_addr(ct, dptr + *matchoff, NULL, addr,
7964ab9e64eSPatrick McHardy 			    dptr + *matchoff + *matchlen))
7974ab9e64eSPatrick McHardy 		return -1;
7984ab9e64eSPatrick McHardy 	return 1;
7994ab9e64eSPatrick McHardy }
8004ab9e64eSPatrick McHardy 
refresh_signalling_expectation(struct nf_conn * ct,union nf_inet_addr * addr,u8 proto,__be16 port,unsigned int expires)8010f32a40fSPatrick McHardy static int refresh_signalling_expectation(struct nf_conn *ct,
8020f32a40fSPatrick McHardy 					  union nf_inet_addr *addr,
803f5b321bdSPatrick McHardy 					  u8 proto, __be16 port,
8040f32a40fSPatrick McHardy 					  unsigned int expires)
8050f32a40fSPatrick McHardy {
8060f32a40fSPatrick McHardy 	struct nf_conn_help *help = nfct_help(ct);
8070f32a40fSPatrick McHardy 	struct nf_conntrack_expect *exp;
808b67bfe0dSSasha Levin 	struct hlist_node *next;
8090f32a40fSPatrick McHardy 	int found = 0;
8100f32a40fSPatrick McHardy 
811ca7433dfSJesper Dangaard Brouer 	spin_lock_bh(&nf_conntrack_expect_lock);
812b67bfe0dSSasha Levin 	hlist_for_each_entry_safe(exp, next, &help->expectations, lnode) {
8130f32a40fSPatrick McHardy 		if (exp->class != SIP_EXPECT_SIGNALLING ||
8140f32a40fSPatrick McHardy 		    !nf_inet_addr_cmp(&exp->tuple.dst.u3, addr) ||
815f5b321bdSPatrick McHardy 		    exp->tuple.dst.protonum != proto ||
8160f32a40fSPatrick McHardy 		    exp->tuple.dst.u.udp.port != port)
8170f32a40fSPatrick McHardy 			continue;
818a96e66e7SGao Feng 		if (mod_timer_pending(&exp->timeout, jiffies + expires * HZ)) {
8190f32a40fSPatrick McHardy 			exp->flags &= ~NF_CT_EXPECT_INACTIVE;
8200f32a40fSPatrick McHardy 			found = 1;
8210f32a40fSPatrick McHardy 			break;
8220f32a40fSPatrick McHardy 		}
823a96e66e7SGao Feng 	}
824ca7433dfSJesper Dangaard Brouer 	spin_unlock_bh(&nf_conntrack_expect_lock);
8250f32a40fSPatrick McHardy 	return found;
8260f32a40fSPatrick McHardy }
8270f32a40fSPatrick McHardy 
flush_expectations(struct nf_conn * ct,bool media)8280f32a40fSPatrick McHardy static void flush_expectations(struct nf_conn *ct, bool media)
8299467ee38SPatrick McHardy {
8309467ee38SPatrick McHardy 	struct nf_conn_help *help = nfct_help(ct);
8319467ee38SPatrick McHardy 	struct nf_conntrack_expect *exp;
832b67bfe0dSSasha Levin 	struct hlist_node *next;
8339467ee38SPatrick McHardy 
834ca7433dfSJesper Dangaard Brouer 	spin_lock_bh(&nf_conntrack_expect_lock);
835b67bfe0dSSasha Levin 	hlist_for_each_entry_safe(exp, next, &help->expectations, lnode) {
8360f32a40fSPatrick McHardy 		if ((exp->class != SIP_EXPECT_SIGNALLING) ^ media)
8370f32a40fSPatrick McHardy 			continue;
838ec0e3f01SGao Feng 		if (!nf_ct_remove_expect(exp))
8399467ee38SPatrick McHardy 			continue;
8400f32a40fSPatrick McHardy 		if (!media)
8410f32a40fSPatrick McHardy 			break;
8429467ee38SPatrick McHardy 	}
843ca7433dfSJesper Dangaard Brouer 	spin_unlock_bh(&nf_conntrack_expect_lock);
8449467ee38SPatrick McHardy }
8459467ee38SPatrick McHardy 
set_expected_rtp_rtcp(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,union nf_inet_addr * daddr,__be16 port,enum sip_expectation_classes class,unsigned int mediaoff,unsigned int medialen)846051966c0SPatrick McHardy static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff,
847051966c0SPatrick McHardy 				 unsigned int dataoff,
848212440a7SPatrick McHardy 				 const char **dptr, unsigned int *datalen,
8494ab9e64eSPatrick McHardy 				 union nf_inet_addr *daddr, __be16 port,
8500d0ab037SPatrick McHardy 				 enum sip_expectation_classes class,
8514ab9e64eSPatrick McHardy 				 unsigned int mediaoff, unsigned int medialen)
8529fafcd7bSPatrick McHardy {
853a9c1d359SPatrick McHardy 	struct nf_conntrack_expect *exp, *rtp_exp, *rtcp_exp;
854212440a7SPatrick McHardy 	enum ip_conntrack_info ctinfo;
855212440a7SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
856a5c3a800SAlexey Dobriyan 	struct net *net = nf_ct_net(ct);
8579fafcd7bSPatrick McHardy 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
858d901a936SPatrick McHardy 	union nf_inet_addr *saddr;
859d901a936SPatrick McHardy 	struct nf_conntrack_tuple tuple;
860c7f485abSPatrick McHardy 	int direct_rtp = 0, skip_expect = 0, ret = NF_DROP;
861a9c1d359SPatrick McHardy 	u_int16_t base_port;
862a9c1d359SPatrick McHardy 	__be16 rtp_port, rtcp_port;
863180cf72fSholger@eitzenberger.org 	const struct nf_nat_sip_hooks *hooks;
8649fafcd7bSPatrick McHardy 
865d901a936SPatrick McHardy 	saddr = NULL;
866d901a936SPatrick McHardy 	if (sip_direct_media) {
867d901a936SPatrick McHardy 		if (!nf_inet_addr_cmp(daddr, &ct->tuplehash[dir].tuple.src.u3))
868d901a936SPatrick McHardy 			return NF_ACCEPT;
869d901a936SPatrick McHardy 		saddr = &ct->tuplehash[!dir].tuple.src.u3;
870a3419ce3SAlin Nastac 	} else if (sip_external_media) {
871a3419ce3SAlin Nastac 		struct net_device *dev = skb_dst(skb)->dev;
872a3419ce3SAlin Nastac 		struct net *net = dev_net(dev);
87329b0b5d5SAlin Nastac 		struct flowi fl;
874a3419ce3SAlin Nastac 		struct dst_entry *dst = NULL;
875a3419ce3SAlin Nastac 
87629b0b5d5SAlin Nastac 		memset(&fl, 0, sizeof(fl));
87729b0b5d5SAlin Nastac 
878a3419ce3SAlin Nastac 		switch (nf_ct_l3num(ct)) {
879a3419ce3SAlin Nastac 			case NFPROTO_IPV4:
88029b0b5d5SAlin Nastac 				fl.u.ip4.daddr = daddr->ip;
88129b0b5d5SAlin Nastac 				nf_ip_route(net, &dst, &fl, false);
882a3419ce3SAlin Nastac 				break;
883a3419ce3SAlin Nastac 
884a3419ce3SAlin Nastac 			case NFPROTO_IPV6:
88529b0b5d5SAlin Nastac 				fl.u.ip6.daddr = daddr->in6;
88629b0b5d5SAlin Nastac 				nf_ip6_route(net, &dst, &fl, false);
887a3419ce3SAlin Nastac 				break;
888a3419ce3SAlin Nastac 		}
889a3419ce3SAlin Nastac 
890a3419ce3SAlin Nastac 		/* Don't predict any conntracks when media endpoint is reachable
891a3419ce3SAlin Nastac 		 * through the same interface as the signalling peer.
892a3419ce3SAlin Nastac 		 */
89329b0b5d5SAlin Nastac 		if (dst) {
89429b0b5d5SAlin Nastac 			bool external_media = (dst->dev == dev);
89529b0b5d5SAlin Nastac 
89629b0b5d5SAlin Nastac 			dst_release(dst);
89729b0b5d5SAlin Nastac 			if (external_media)
898a3419ce3SAlin Nastac 				return NF_ACCEPT;
899d901a936SPatrick McHardy 		}
90029b0b5d5SAlin Nastac 	}
901d901a936SPatrick McHardy 
902d901a936SPatrick McHardy 	/* We need to check whether the registration exists before attempting
903d901a936SPatrick McHardy 	 * to register it since we can see the same media description multiple
904d901a936SPatrick McHardy 	 * times on different connections in case multiple endpoints receive
905d901a936SPatrick McHardy 	 * the same call.
906c7f485abSPatrick McHardy 	 *
907c7f485abSPatrick McHardy 	 * RTP optimization: if we find a matching media channel expectation
908c7f485abSPatrick McHardy 	 * and both the expectation and this connection are SNATed, we assume
909c7f485abSPatrick McHardy 	 * both sides can reach each other directly and use the final
910c7f485abSPatrick McHardy 	 * destination address from the expectation. We still need to keep
911c7f485abSPatrick McHardy 	 * the NATed expectations for media that might arrive from the
912c7f485abSPatrick McHardy 	 * outside, and additionally need to expect the direct RTP stream
913c7f485abSPatrick McHardy 	 * in case it passes through us even without NAT.
914d901a936SPatrick McHardy 	 */
915d901a936SPatrick McHardy 	memset(&tuple, 0, sizeof(tuple));
916d901a936SPatrick McHardy 	if (saddr)
917d901a936SPatrick McHardy 		tuple.src.u3 = *saddr;
9185e8fbe2aSPatrick McHardy 	tuple.src.l3num		= nf_ct_l3num(ct);
919d901a936SPatrick McHardy 	tuple.dst.protonum	= IPPROTO_UDP;
920d901a936SPatrick McHardy 	tuple.dst.u3		= *daddr;
921d901a936SPatrick McHardy 	tuple.dst.u.udp.port	= port;
922d901a936SPatrick McHardy 
923c7f485abSPatrick McHardy 	do {
9245d0aa2ccSPatrick McHardy 		exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &tuple);
925d901a936SPatrick McHardy 
926c7f485abSPatrick McHardy 		if (!exp || exp->master == ct ||
927c7f485abSPatrick McHardy 		    nfct_help(exp->master)->helper != nfct_help(ct)->helper ||
928c7f485abSPatrick McHardy 		    exp->class != class)
929c7f485abSPatrick McHardy 			break;
9304806e975SFlorian Westphal #if IS_ENABLED(CONFIG_NF_NAT)
9319a664821SPatrick McHardy 		if (!direct_rtp &&
9329a664821SPatrick McHardy 		    (!nf_inet_addr_cmp(&exp->saved_addr, &exp->tuple.dst.u3) ||
933c7f485abSPatrick McHardy 		     exp->saved_proto.udp.port != exp->tuple.dst.u.udp.port) &&
934c7f485abSPatrick McHardy 		    ct->status & IPS_NAT_MASK) {
9359a664821SPatrick McHardy 			*daddr			= exp->saved_addr;
9369a664821SPatrick McHardy 			tuple.dst.u3		= exp->saved_addr;
937c7f485abSPatrick McHardy 			tuple.dst.u.udp.port	= exp->saved_proto.udp.port;
938c7f485abSPatrick McHardy 			direct_rtp = 1;
939c7f485abSPatrick McHardy 		} else
940e1f9a464SPatrick McHardy #endif
941c7f485abSPatrick McHardy 			skip_expect = 1;
942c7f485abSPatrick McHardy 	} while (!skip_expect);
943d901a936SPatrick McHardy 
944a9c1d359SPatrick McHardy 	base_port = ntohs(tuple.dst.u.udp.port) & ~1;
945a9c1d359SPatrick McHardy 	rtp_port = htons(base_port);
946a9c1d359SPatrick McHardy 	rtcp_port = htons(base_port + 1);
947a9c1d359SPatrick McHardy 
948c7f485abSPatrick McHardy 	if (direct_rtp) {
949180cf72fSholger@eitzenberger.org 		hooks = rcu_dereference(nf_nat_sip_hooks);
950180cf72fSholger@eitzenberger.org 		if (hooks &&
951180cf72fSholger@eitzenberger.org 		    !hooks->sdp_port(skb, protoff, dataoff, dptr, datalen,
952c7f485abSPatrick McHardy 				     mediaoff, medialen, ntohs(rtp_port)))
953c7f485abSPatrick McHardy 			goto err1;
954c7f485abSPatrick McHardy 	}
955c7f485abSPatrick McHardy 
9560b35f603STaehee Yoo 	if (skip_expect)
957c7f485abSPatrick McHardy 		return NF_ACCEPT;
958c7f485abSPatrick McHardy 
959a9c1d359SPatrick McHardy 	rtp_exp = nf_ct_expect_alloc(ct);
960a9c1d359SPatrick McHardy 	if (rtp_exp == NULL)
961a9c1d359SPatrick McHardy 		goto err1;
9625e8fbe2aSPatrick McHardy 	nf_ct_expect_init(rtp_exp, class, nf_ct_l3num(ct), saddr, daddr,
963a9c1d359SPatrick McHardy 			  IPPROTO_UDP, NULL, &rtp_port);
964a9c1d359SPatrick McHardy 
965a9c1d359SPatrick McHardy 	rtcp_exp = nf_ct_expect_alloc(ct);
966a9c1d359SPatrick McHardy 	if (rtcp_exp == NULL)
967a9c1d359SPatrick McHardy 		goto err2;
9685e8fbe2aSPatrick McHardy 	nf_ct_expect_init(rtcp_exp, class, nf_ct_l3num(ct), saddr, daddr,
969a9c1d359SPatrick McHardy 			  IPPROTO_UDP, NULL, &rtcp_port);
9709fafcd7bSPatrick McHardy 
971180cf72fSholger@eitzenberger.org 	hooks = rcu_dereference(nf_nat_sip_hooks);
972180cf72fSholger@eitzenberger.org 	if (hooks && ct->status & IPS_NAT_MASK && !direct_rtp)
973180cf72fSholger@eitzenberger.org 		ret = hooks->sdp_media(skb, protoff, dataoff, dptr,
974180cf72fSholger@eitzenberger.org 				       datalen, rtp_exp, rtcp_exp,
9754ab9e64eSPatrick McHardy 				       mediaoff, medialen, daddr);
9769fafcd7bSPatrick McHardy 	else {
977876c2731SFlorian Westphal 		/* -EALREADY handling works around end-points that send
978876c2731SFlorian Westphal 		 * SDP messages with identical port but different media type,
979876c2731SFlorian Westphal 		 * we pretend expectation was set up.
9803c00fb0bSxiao ruizhu 		 * It also works in the case that SDP messages are sent with
9813c00fb0bSxiao ruizhu 		 * identical expect tuples but for different master conntracks.
982876c2731SFlorian Westphal 		 */
9833c00fb0bSxiao ruizhu 		int errp = nf_ct_expect_related(rtp_exp,
9843c00fb0bSxiao ruizhu 						NF_CT_EXP_F_SKIP_MASTER);
985876c2731SFlorian Westphal 
986876c2731SFlorian Westphal 		if (errp == 0 || errp == -EALREADY) {
9873c00fb0bSxiao ruizhu 			int errcp = nf_ct_expect_related(rtcp_exp,
9883c00fb0bSxiao ruizhu 						NF_CT_EXP_F_SKIP_MASTER);
989876c2731SFlorian Westphal 
990876c2731SFlorian Westphal 			if (errcp == 0 || errcp == -EALREADY)
9919fafcd7bSPatrick McHardy 				ret = NF_ACCEPT;
992876c2731SFlorian Westphal 			else if (errp == 0)
993876c2731SFlorian Westphal 				nf_ct_unexpect_related(rtp_exp);
9949fafcd7bSPatrick McHardy 		}
995a9c1d359SPatrick McHardy 	}
996a9c1d359SPatrick McHardy 	nf_ct_expect_put(rtcp_exp);
997a9c1d359SPatrick McHardy err2:
998a9c1d359SPatrick McHardy 	nf_ct_expect_put(rtp_exp);
999a9c1d359SPatrick McHardy err1:
10009fafcd7bSPatrick McHardy 	return ret;
10019fafcd7bSPatrick McHardy }
10029fafcd7bSPatrick McHardy 
10030d0ab037SPatrick McHardy static const struct sdp_media_type sdp_media_types[] = {
10040d0ab037SPatrick McHardy 	SDP_MEDIA_TYPE("audio ", SIP_EXPECT_AUDIO),
10050d0ab037SPatrick McHardy 	SDP_MEDIA_TYPE("video ", SIP_EXPECT_VIDEO),
10069d288dffSPatrick McHardy 	SDP_MEDIA_TYPE("image ", SIP_EXPECT_IMAGE),
10070d0ab037SPatrick McHardy };
10080d0ab037SPatrick McHardy 
sdp_media_type(const char * dptr,unsigned int matchoff,unsigned int matchlen)10090d0ab037SPatrick McHardy static const struct sdp_media_type *sdp_media_type(const char *dptr,
10100d0ab037SPatrick McHardy 						   unsigned int matchoff,
10110d0ab037SPatrick McHardy 						   unsigned int matchlen)
10120d0ab037SPatrick McHardy {
10130d0ab037SPatrick McHardy 	const struct sdp_media_type *t;
10140d0ab037SPatrick McHardy 	unsigned int i;
10150d0ab037SPatrick McHardy 
10160d0ab037SPatrick McHardy 	for (i = 0; i < ARRAY_SIZE(sdp_media_types); i++) {
10170d0ab037SPatrick McHardy 		t = &sdp_media_types[i];
10180d0ab037SPatrick McHardy 		if (matchlen < t->len ||
10190d0ab037SPatrick McHardy 		    strncmp(dptr + matchoff, t->name, t->len))
10200d0ab037SPatrick McHardy 			continue;
10210d0ab037SPatrick McHardy 		return t;
10220d0ab037SPatrick McHardy 	}
10230d0ab037SPatrick McHardy 	return NULL;
10240d0ab037SPatrick McHardy }
10250d0ab037SPatrick McHardy 
process_sdp(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq)1026051966c0SPatrick McHardy static int process_sdp(struct sk_buff *skb, unsigned int protoff,
1027051966c0SPatrick McHardy 		       unsigned int dataoff,
102830f33e6dSPatrick McHardy 		       const char **dptr, unsigned int *datalen,
102930f33e6dSPatrick McHardy 		       unsigned int cseq)
10307d3dd043SPatrick McHardy {
10317d3dd043SPatrick McHardy 	enum ip_conntrack_info ctinfo;
10327d3dd043SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
10337d3dd043SPatrick McHardy 	unsigned int matchoff, matchlen;
10344ab9e64eSPatrick McHardy 	unsigned int mediaoff, medialen;
10354ab9e64eSPatrick McHardy 	unsigned int sdpoff;
10364ab9e64eSPatrick McHardy 	unsigned int caddr_len, maddr_len;
10370d0ab037SPatrick McHardy 	unsigned int i;
10384ab9e64eSPatrick McHardy 	union nf_inet_addr caddr, maddr, rtp_addr;
1039180cf72fSholger@eitzenberger.org 	const struct nf_nat_sip_hooks *hooks;
10407d3dd043SPatrick McHardy 	unsigned int port;
10410d0ab037SPatrick McHardy 	const struct sdp_media_type *t;
10420d0ab037SPatrick McHardy 	int ret = NF_ACCEPT;
10437d3dd043SPatrick McHardy 
1044180cf72fSholger@eitzenberger.org 	hooks = rcu_dereference(nf_nat_sip_hooks);
10457d3dd043SPatrick McHardy 
10464ab9e64eSPatrick McHardy 	/* Find beginning of session description */
10477d3dd043SPatrick McHardy 	if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
10484ab9e64eSPatrick McHardy 				  SDP_HDR_VERSION, SDP_HDR_UNSPEC,
10497d3dd043SPatrick McHardy 				  &matchoff, &matchlen) <= 0)
10507d3dd043SPatrick McHardy 		return NF_ACCEPT;
10514ab9e64eSPatrick McHardy 	sdpoff = matchoff;
10527d3dd043SPatrick McHardy 
10534ab9e64eSPatrick McHardy 	/* The connection information is contained in the session description
10544ab9e64eSPatrick McHardy 	 * and/or once per media description. The first media description marks
10554ab9e64eSPatrick McHardy 	 * the end of the session description. */
10564ab9e64eSPatrick McHardy 	caddr_len = 0;
10574ab9e64eSPatrick McHardy 	if (ct_sip_parse_sdp_addr(ct, *dptr, sdpoff, *datalen,
10589a664821SPatrick McHardy 				  SDP_HDR_CONNECTION, SDP_HDR_MEDIA,
10594ab9e64eSPatrick McHardy 				  &matchoff, &matchlen, &caddr) > 0)
10604ab9e64eSPatrick McHardy 		caddr_len = matchlen;
10617d3dd043SPatrick McHardy 
10620d0ab037SPatrick McHardy 	mediaoff = sdpoff;
10630d0ab037SPatrick McHardy 	for (i = 0; i < ARRAY_SIZE(sdp_media_types); ) {
10640d0ab037SPatrick McHardy 		if (ct_sip_get_sdp_header(ct, *dptr, mediaoff, *datalen,
10657d3dd043SPatrick McHardy 					  SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
10664ab9e64eSPatrick McHardy 					  &mediaoff, &medialen) <= 0)
10670d0ab037SPatrick McHardy 			break;
10680d0ab037SPatrick McHardy 
10690d0ab037SPatrick McHardy 		/* Get media type and port number. A media port value of zero
10700d0ab037SPatrick McHardy 		 * indicates an inactive stream. */
10710d0ab037SPatrick McHardy 		t = sdp_media_type(*dptr, mediaoff, medialen);
10720d0ab037SPatrick McHardy 		if (!t) {
10730d0ab037SPatrick McHardy 			mediaoff += medialen;
10740d0ab037SPatrick McHardy 			continue;
10750d0ab037SPatrick McHardy 		}
10760d0ab037SPatrick McHardy 		mediaoff += t->len;
10770d0ab037SPatrick McHardy 		medialen -= t->len;
10787d3dd043SPatrick McHardy 
10794ab9e64eSPatrick McHardy 		port = simple_strtoul(*dptr + mediaoff, NULL, 10);
10800d0ab037SPatrick McHardy 		if (port == 0)
10810d0ab037SPatrick McHardy 			continue;
1082b20ab9ccSPablo Neira Ayuso 		if (port < 1024 || port > 65535) {
1083b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "wrong port %u", port);
10847d3dd043SPatrick McHardy 			return NF_DROP;
1085b20ab9ccSPablo Neira Ayuso 		}
10867d3dd043SPatrick McHardy 
10874ab9e64eSPatrick McHardy 		/* The media description overrides the session description. */
10884ab9e64eSPatrick McHardy 		maddr_len = 0;
10894ab9e64eSPatrick McHardy 		if (ct_sip_parse_sdp_addr(ct, *dptr, mediaoff, *datalen,
10909a664821SPatrick McHardy 					  SDP_HDR_CONNECTION, SDP_HDR_MEDIA,
10914ab9e64eSPatrick McHardy 					  &matchoff, &matchlen, &maddr) > 0) {
10924ab9e64eSPatrick McHardy 			maddr_len = matchlen;
10934ab9e64eSPatrick McHardy 			memcpy(&rtp_addr, &maddr, sizeof(rtp_addr));
10944ab9e64eSPatrick McHardy 		} else if (caddr_len)
10954ab9e64eSPatrick McHardy 			memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
1096b20ab9ccSPablo Neira Ayuso 		else {
1097b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "cannot parse SDP message");
10984ab9e64eSPatrick McHardy 			return NF_DROP;
1099b20ab9ccSPablo Neira Ayuso 		}
11004ab9e64eSPatrick McHardy 
1101051966c0SPatrick McHardy 		ret = set_expected_rtp_rtcp(skb, protoff, dataoff,
1102051966c0SPatrick McHardy 					    dptr, datalen,
11030d0ab037SPatrick McHardy 					    &rtp_addr, htons(port), t->class,
11044ab9e64eSPatrick McHardy 					    mediaoff, medialen);
1105b20ab9ccSPablo Neira Ayuso 		if (ret != NF_ACCEPT) {
1106b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct,
1107b20ab9ccSPablo Neira Ayuso 					 "cannot add expectation for voice");
11084ab9e64eSPatrick McHardy 			return ret;
1109b20ab9ccSPablo Neira Ayuso 		}
11104ab9e64eSPatrick McHardy 
11114ab9e64eSPatrick McHardy 		/* Update media connection address if present */
1112180cf72fSholger@eitzenberger.org 		if (maddr_len && hooks && ct->status & IPS_NAT_MASK) {
1113180cf72fSholger@eitzenberger.org 			ret = hooks->sdp_addr(skb, protoff, dataoff,
11149a664821SPatrick McHardy 					      dptr, datalen, mediaoff,
1115180cf72fSholger@eitzenberger.org 					      SDP_HDR_CONNECTION,
1116180cf72fSholger@eitzenberger.org 					      SDP_HDR_MEDIA,
11173b6b9fabSPatrick McHardy 					      &rtp_addr);
1118b20ab9ccSPablo Neira Ayuso 			if (ret != NF_ACCEPT) {
1119b20ab9ccSPablo Neira Ayuso 				nf_ct_helper_log(skb, ct, "cannot mangle SDP");
11204ab9e64eSPatrick McHardy 				return ret;
11214ab9e64eSPatrick McHardy 			}
1122b20ab9ccSPablo Neira Ayuso 		}
11230d0ab037SPatrick McHardy 		i++;
11244ab9e64eSPatrick McHardy 	}
11254ab9e64eSPatrick McHardy 
11264ab9e64eSPatrick McHardy 	/* Update session connection and owner addresses */
1127180cf72fSholger@eitzenberger.org 	hooks = rcu_dereference(nf_nat_sip_hooks);
1128180cf72fSholger@eitzenberger.org 	if (hooks && ct->status & IPS_NAT_MASK)
1129180cf72fSholger@eitzenberger.org 		ret = hooks->sdp_session(skb, protoff, dataoff,
1130180cf72fSholger@eitzenberger.org 					 dptr, datalen, sdpoff,
1131180cf72fSholger@eitzenberger.org 					 &rtp_addr);
11324ab9e64eSPatrick McHardy 
11334ab9e64eSPatrick McHardy 	return ret;
11347d3dd043SPatrick McHardy }
process_invite_response(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq,unsigned int code)1135051966c0SPatrick McHardy static int process_invite_response(struct sk_buff *skb, unsigned int protoff,
1136051966c0SPatrick McHardy 				   unsigned int dataoff,
113730f33e6dSPatrick McHardy 				   const char **dptr, unsigned int *datalen,
113830f33e6dSPatrick McHardy 				   unsigned int cseq, unsigned int code)
113930f33e6dSPatrick McHardy {
11409467ee38SPatrick McHardy 	enum ip_conntrack_info ctinfo;
11419467ee38SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
11421afc5679SPablo Neira Ayuso 	struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
11439467ee38SPatrick McHardy 
114430f33e6dSPatrick McHardy 	if ((code >= 100 && code <= 199) ||
114530f33e6dSPatrick McHardy 	    (code >= 200 && code <= 299))
1146051966c0SPatrick McHardy 		return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
11471afc5679SPablo Neira Ayuso 	else if (ct_sip_info->invite_cseq == cseq)
11480f32a40fSPatrick McHardy 		flush_expectations(ct, true);
114930f33e6dSPatrick McHardy 	return NF_ACCEPT;
115030f33e6dSPatrick McHardy }
115130f33e6dSPatrick McHardy 
process_update_response(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq,unsigned int code)1152051966c0SPatrick McHardy static int process_update_response(struct sk_buff *skb, unsigned int protoff,
1153051966c0SPatrick McHardy 				   unsigned int dataoff,
115430f33e6dSPatrick McHardy 				   const char **dptr, unsigned int *datalen,
115530f33e6dSPatrick McHardy 				   unsigned int cseq, unsigned int code)
115630f33e6dSPatrick McHardy {
11579467ee38SPatrick McHardy 	enum ip_conntrack_info ctinfo;
11589467ee38SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
11591afc5679SPablo Neira Ayuso 	struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
11609467ee38SPatrick McHardy 
116130f33e6dSPatrick McHardy 	if ((code >= 100 && code <= 199) ||
116230f33e6dSPatrick McHardy 	    (code >= 200 && code <= 299))
1163051966c0SPatrick McHardy 		return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
11641afc5679SPablo Neira Ayuso 	else if (ct_sip_info->invite_cseq == cseq)
11650f32a40fSPatrick McHardy 		flush_expectations(ct, true);
116630f33e6dSPatrick McHardy 	return NF_ACCEPT;
116730f33e6dSPatrick McHardy }
116830f33e6dSPatrick McHardy 
process_prack_response(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq,unsigned int code)1169051966c0SPatrick McHardy static int process_prack_response(struct sk_buff *skb, unsigned int protoff,
1170051966c0SPatrick McHardy 				  unsigned int dataoff,
1171595a8ecbSPatrick McHardy 				  const char **dptr, unsigned int *datalen,
1172595a8ecbSPatrick McHardy 				  unsigned int cseq, unsigned int code)
1173595a8ecbSPatrick McHardy {
11749467ee38SPatrick McHardy 	enum ip_conntrack_info ctinfo;
11759467ee38SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
11761afc5679SPablo Neira Ayuso 	struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
11779467ee38SPatrick McHardy 
1178595a8ecbSPatrick McHardy 	if ((code >= 100 && code <= 199) ||
1179595a8ecbSPatrick McHardy 	    (code >= 200 && code <= 299))
1180051966c0SPatrick McHardy 		return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
11811afc5679SPablo Neira Ayuso 	else if (ct_sip_info->invite_cseq == cseq)
11820f32a40fSPatrick McHardy 		flush_expectations(ct, true);
11839467ee38SPatrick McHardy 	return NF_ACCEPT;
11849467ee38SPatrick McHardy }
1185595a8ecbSPatrick McHardy 
process_invite_request(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq)1186051966c0SPatrick McHardy static int process_invite_request(struct sk_buff *skb, unsigned int protoff,
1187051966c0SPatrick McHardy 				  unsigned int dataoff,
11889d288dffSPatrick McHardy 				  const char **dptr, unsigned int *datalen,
11899d288dffSPatrick McHardy 				  unsigned int cseq)
11909d288dffSPatrick McHardy {
11919d288dffSPatrick McHardy 	enum ip_conntrack_info ctinfo;
11929d288dffSPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
11931afc5679SPablo Neira Ayuso 	struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
11949d288dffSPatrick McHardy 	unsigned int ret;
11959d288dffSPatrick McHardy 
11969d288dffSPatrick McHardy 	flush_expectations(ct, true);
1197051966c0SPatrick McHardy 	ret = process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
11989d288dffSPatrick McHardy 	if (ret == NF_ACCEPT)
11991afc5679SPablo Neira Ayuso 		ct_sip_info->invite_cseq = cseq;
12009d288dffSPatrick McHardy 	return ret;
12019d288dffSPatrick McHardy }
12029d288dffSPatrick McHardy 
process_bye_request(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq)1203051966c0SPatrick McHardy static int process_bye_request(struct sk_buff *skb, unsigned int protoff,
1204051966c0SPatrick McHardy 			       unsigned int dataoff,
12059467ee38SPatrick McHardy 			       const char **dptr, unsigned int *datalen,
12069467ee38SPatrick McHardy 			       unsigned int cseq)
12079467ee38SPatrick McHardy {
12089467ee38SPatrick McHardy 	enum ip_conntrack_info ctinfo;
12099467ee38SPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
12109467ee38SPatrick McHardy 
12110f32a40fSPatrick McHardy 	flush_expectations(ct, true);
12120f32a40fSPatrick McHardy 	return NF_ACCEPT;
12130f32a40fSPatrick McHardy }
12140f32a40fSPatrick McHardy 
12150f32a40fSPatrick McHardy /* Parse a REGISTER request and create a permanent expectation for incoming
12160f32a40fSPatrick McHardy  * signalling connections. The expectation is marked inactive and is activated
12170f32a40fSPatrick McHardy  * when receiving a response indicating success from the registrar.
12180f32a40fSPatrick McHardy  */
process_register_request(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq)1219051966c0SPatrick McHardy static int process_register_request(struct sk_buff *skb, unsigned int protoff,
1220051966c0SPatrick McHardy 				    unsigned int dataoff,
12210f32a40fSPatrick McHardy 				    const char **dptr, unsigned int *datalen,
12220f32a40fSPatrick McHardy 				    unsigned int cseq)
12230f32a40fSPatrick McHardy {
12240f32a40fSPatrick McHardy 	enum ip_conntrack_info ctinfo;
12250f32a40fSPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
12261afc5679SPablo Neira Ayuso 	struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
12270f32a40fSPatrick McHardy 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
12280f32a40fSPatrick McHardy 	unsigned int matchoff, matchlen;
12290f32a40fSPatrick McHardy 	struct nf_conntrack_expect *exp;
12300f32a40fSPatrick McHardy 	union nf_inet_addr *saddr, daddr;
1231180cf72fSholger@eitzenberger.org 	const struct nf_nat_sip_hooks *hooks;
1232e14575faSFlorian Westphal 	struct nf_conntrack_helper *helper;
12330f32a40fSPatrick McHardy 	__be16 port;
1234f5b321bdSPatrick McHardy 	u8 proto;
12350f32a40fSPatrick McHardy 	unsigned int expires = 0;
12360f32a40fSPatrick McHardy 	int ret;
12370f32a40fSPatrick McHardy 
12380f32a40fSPatrick McHardy 	/* Expected connections can not register again. */
12390f32a40fSPatrick McHardy 	if (ct->status & IPS_EXPECTED)
12400f32a40fSPatrick McHardy 		return NF_ACCEPT;
12410f32a40fSPatrick McHardy 
12420f32a40fSPatrick McHardy 	/* We must check the expiration time: a value of zero signals the
12430f32a40fSPatrick McHardy 	 * registrar to release the binding. We'll remove our expectation
12440f32a40fSPatrick McHardy 	 * when receiving the new bindings in the response, but we don't
12450f32a40fSPatrick McHardy 	 * want to create new ones.
12460f32a40fSPatrick McHardy 	 *
12470f32a40fSPatrick McHardy 	 * The expiration time may be contained in Expires: header, the
12480f32a40fSPatrick McHardy 	 * Contact: header parameters or the URI parameters.
12490f32a40fSPatrick McHardy 	 */
12500f32a40fSPatrick McHardy 	if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_EXPIRES,
12510f32a40fSPatrick McHardy 			      &matchoff, &matchlen) > 0)
12520f32a40fSPatrick McHardy 		expires = simple_strtoul(*dptr + matchoff, NULL, 10);
12530f32a40fSPatrick McHardy 
12540f32a40fSPatrick McHardy 	ret = ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen,
12550f32a40fSPatrick McHardy 				      SIP_HDR_CONTACT, NULL,
12560f32a40fSPatrick McHardy 				      &matchoff, &matchlen, &daddr, &port);
1257b20ab9ccSPablo Neira Ayuso 	if (ret < 0) {
1258b20ab9ccSPablo Neira Ayuso 		nf_ct_helper_log(skb, ct, "cannot parse contact");
12590f32a40fSPatrick McHardy 		return NF_DROP;
1260b20ab9ccSPablo Neira Ayuso 	} else if (ret == 0)
12610f32a40fSPatrick McHardy 		return NF_ACCEPT;
12620f32a40fSPatrick McHardy 
12630f32a40fSPatrick McHardy 	/* We don't support third-party registrations */
12640f32a40fSPatrick McHardy 	if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, &daddr))
12650f32a40fSPatrick McHardy 		return NF_ACCEPT;
12660f32a40fSPatrick McHardy 
1267f5b321bdSPatrick McHardy 	if (ct_sip_parse_transport(ct, *dptr, matchoff + matchlen, *datalen,
1268f5b321bdSPatrick McHardy 				   &proto) == 0)
1269f5b321bdSPatrick McHardy 		return NF_ACCEPT;
1270f5b321bdSPatrick McHardy 
12710f32a40fSPatrick McHardy 	if (ct_sip_parse_numerical_param(ct, *dptr,
12720f32a40fSPatrick McHardy 					 matchoff + matchlen, *datalen,
1273b20ab9ccSPablo Neira Ayuso 					 "expires=", NULL, NULL, &expires) < 0) {
1274b20ab9ccSPablo Neira Ayuso 		nf_ct_helper_log(skb, ct, "cannot parse expires");
12750f32a40fSPatrick McHardy 		return NF_DROP;
1276b20ab9ccSPablo Neira Ayuso 	}
12770f32a40fSPatrick McHardy 
12780f32a40fSPatrick McHardy 	if (expires == 0) {
12790f32a40fSPatrick McHardy 		ret = NF_ACCEPT;
12800f32a40fSPatrick McHardy 		goto store_cseq;
12810f32a40fSPatrick McHardy 	}
12820f32a40fSPatrick McHardy 
12830f32a40fSPatrick McHardy 	exp = nf_ct_expect_alloc(ct);
1284b20ab9ccSPablo Neira Ayuso 	if (!exp) {
1285b20ab9ccSPablo Neira Ayuso 		nf_ct_helper_log(skb, ct, "cannot alloc expectation");
12860f32a40fSPatrick McHardy 		return NF_DROP;
1287b20ab9ccSPablo Neira Ayuso 	}
12880f32a40fSPatrick McHardy 
12890f32a40fSPatrick McHardy 	saddr = NULL;
12900f32a40fSPatrick McHardy 	if (sip_direct_signalling)
12910f32a40fSPatrick McHardy 		saddr = &ct->tuplehash[!dir].tuple.src.u3;
12920f32a40fSPatrick McHardy 
1293e14575faSFlorian Westphal 	helper = rcu_dereference(nfct_help(ct)->helper);
1294e14575faSFlorian Westphal 	if (!helper)
1295e14575faSFlorian Westphal 		return NF_DROP;
1296e14575faSFlorian Westphal 
12975e8fbe2aSPatrick McHardy 	nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
1298f5b321bdSPatrick McHardy 			  saddr, &daddr, proto, NULL, &port);
12990f32a40fSPatrick McHardy 	exp->timeout.expires = sip_timeout * HZ;
1300e14575faSFlorian Westphal 	exp->helper = helper;
13010f32a40fSPatrick McHardy 	exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
13020f32a40fSPatrick McHardy 
1303180cf72fSholger@eitzenberger.org 	hooks = rcu_dereference(nf_nat_sip_hooks);
1304180cf72fSholger@eitzenberger.org 	if (hooks && ct->status & IPS_NAT_MASK)
1305180cf72fSholger@eitzenberger.org 		ret = hooks->expect(skb, protoff, dataoff, dptr, datalen,
1306051966c0SPatrick McHardy 				    exp, matchoff, matchlen);
13070f32a40fSPatrick McHardy 	else {
13083c00fb0bSxiao ruizhu 		if (nf_ct_expect_related(exp, 0) != 0) {
1309b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "cannot add expectation");
13100f32a40fSPatrick McHardy 			ret = NF_DROP;
1311b20ab9ccSPablo Neira Ayuso 		} else
13120f32a40fSPatrick McHardy 			ret = NF_ACCEPT;
13130f32a40fSPatrick McHardy 	}
13140f32a40fSPatrick McHardy 	nf_ct_expect_put(exp);
13150f32a40fSPatrick McHardy 
13160f32a40fSPatrick McHardy store_cseq:
13170f32a40fSPatrick McHardy 	if (ret == NF_ACCEPT)
13181afc5679SPablo Neira Ayuso 		ct_sip_info->register_cseq = cseq;
13190f32a40fSPatrick McHardy 	return ret;
13200f32a40fSPatrick McHardy }
13210f32a40fSPatrick McHardy 
process_register_response(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen,unsigned int cseq,unsigned int code)1322051966c0SPatrick McHardy static int process_register_response(struct sk_buff *skb, unsigned int protoff,
1323051966c0SPatrick McHardy 				     unsigned int dataoff,
13240f32a40fSPatrick McHardy 				     const char **dptr, unsigned int *datalen,
13250f32a40fSPatrick McHardy 				     unsigned int cseq, unsigned int code)
13260f32a40fSPatrick McHardy {
13270f32a40fSPatrick McHardy 	enum ip_conntrack_info ctinfo;
13280f32a40fSPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
13291afc5679SPablo Neira Ayuso 	struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
13300f32a40fSPatrick McHardy 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
13310f32a40fSPatrick McHardy 	union nf_inet_addr addr;
13320f32a40fSPatrick McHardy 	__be16 port;
1333f5b321bdSPatrick McHardy 	u8 proto;
13343b6b9fabSPatrick McHardy 	unsigned int matchoff, matchlen, coff = 0;
13350f32a40fSPatrick McHardy 	unsigned int expires = 0;
13360f32a40fSPatrick McHardy 	int in_contact = 0, ret;
13370f32a40fSPatrick McHardy 
13380f32a40fSPatrick McHardy 	/* According to RFC 3261, "UAs MUST NOT send a new registration until
13390f32a40fSPatrick McHardy 	 * they have received a final response from the registrar for the
13400f32a40fSPatrick McHardy 	 * previous one or the previous REGISTER request has timed out".
13410f32a40fSPatrick McHardy 	 *
13420f32a40fSPatrick McHardy 	 * However, some servers fail to detect retransmissions and send late
13430f32a40fSPatrick McHardy 	 * responses, so we store the sequence number of the last valid
13440f32a40fSPatrick McHardy 	 * request and compare it here.
13450f32a40fSPatrick McHardy 	 */
13461afc5679SPablo Neira Ayuso 	if (ct_sip_info->register_cseq != cseq)
13470f32a40fSPatrick McHardy 		return NF_ACCEPT;
13480f32a40fSPatrick McHardy 
13490f32a40fSPatrick McHardy 	if (code >= 100 && code <= 199)
13500f32a40fSPatrick McHardy 		return NF_ACCEPT;
13510f32a40fSPatrick McHardy 	if (code < 200 || code > 299)
13520f32a40fSPatrick McHardy 		goto flush;
13530f32a40fSPatrick McHardy 
13540f32a40fSPatrick McHardy 	if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_EXPIRES,
13550f32a40fSPatrick McHardy 			      &matchoff, &matchlen) > 0)
13560f32a40fSPatrick McHardy 		expires = simple_strtoul(*dptr + matchoff, NULL, 10);
13570f32a40fSPatrick McHardy 
13580f32a40fSPatrick McHardy 	while (1) {
13590f32a40fSPatrick McHardy 		unsigned int c_expires = expires;
13600f32a40fSPatrick McHardy 
13613b6b9fabSPatrick McHardy 		ret = ct_sip_parse_header_uri(ct, *dptr, &coff, *datalen,
13620f32a40fSPatrick McHardy 					      SIP_HDR_CONTACT, &in_contact,
13630f32a40fSPatrick McHardy 					      &matchoff, &matchlen,
13640f32a40fSPatrick McHardy 					      &addr, &port);
1365b20ab9ccSPablo Neira Ayuso 		if (ret < 0) {
1366b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "cannot parse contact");
13670f32a40fSPatrick McHardy 			return NF_DROP;
1368b20ab9ccSPablo Neira Ayuso 		} else if (ret == 0)
13690f32a40fSPatrick McHardy 			break;
13700f32a40fSPatrick McHardy 
13710f32a40fSPatrick McHardy 		/* We don't support third-party registrations */
13720f32a40fSPatrick McHardy 		if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3, &addr))
13730f32a40fSPatrick McHardy 			continue;
13740f32a40fSPatrick McHardy 
1375f5b321bdSPatrick McHardy 		if (ct_sip_parse_transport(ct, *dptr, matchoff + matchlen,
1376f5b321bdSPatrick McHardy 					   *datalen, &proto) == 0)
1377f5b321bdSPatrick McHardy 			continue;
1378f5b321bdSPatrick McHardy 
13790f32a40fSPatrick McHardy 		ret = ct_sip_parse_numerical_param(ct, *dptr,
13800f32a40fSPatrick McHardy 						   matchoff + matchlen,
13810f32a40fSPatrick McHardy 						   *datalen, "expires=",
13820f32a40fSPatrick McHardy 						   NULL, NULL, &c_expires);
1383b20ab9ccSPablo Neira Ayuso 		if (ret < 0) {
1384b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "cannot parse expires");
13850f32a40fSPatrick McHardy 			return NF_DROP;
1386b20ab9ccSPablo Neira Ayuso 		}
13870f32a40fSPatrick McHardy 		if (c_expires == 0)
13880f32a40fSPatrick McHardy 			break;
1389f5b321bdSPatrick McHardy 		if (refresh_signalling_expectation(ct, &addr, proto, port,
1390f5b321bdSPatrick McHardy 						   c_expires))
13910f32a40fSPatrick McHardy 			return NF_ACCEPT;
13920f32a40fSPatrick McHardy 	}
13930f32a40fSPatrick McHardy 
13940f32a40fSPatrick McHardy flush:
13950f32a40fSPatrick McHardy 	flush_expectations(ct, false);
1396595a8ecbSPatrick McHardy 	return NF_ACCEPT;
1397595a8ecbSPatrick McHardy }
1398595a8ecbSPatrick McHardy 
139930f33e6dSPatrick McHardy static const struct sip_handler sip_handlers[] = {
14009d288dffSPatrick McHardy 	SIP_HANDLER("INVITE", process_invite_request, process_invite_response),
140130f33e6dSPatrick McHardy 	SIP_HANDLER("UPDATE", process_sdp, process_update_response),
1402595a8ecbSPatrick McHardy 	SIP_HANDLER("ACK", process_sdp, NULL),
1403595a8ecbSPatrick McHardy 	SIP_HANDLER("PRACK", process_sdp, process_prack_response),
14049467ee38SPatrick McHardy 	SIP_HANDLER("BYE", process_bye_request, NULL),
14050f32a40fSPatrick McHardy 	SIP_HANDLER("REGISTER", process_register_request, process_register_response),
140630f33e6dSPatrick McHardy };
140730f33e6dSPatrick McHardy 
process_sip_response(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen)1408051966c0SPatrick McHardy static int process_sip_response(struct sk_buff *skb, unsigned int protoff,
1409051966c0SPatrick McHardy 				unsigned int dataoff,
141030f33e6dSPatrick McHardy 				const char **dptr, unsigned int *datalen)
141130f33e6dSPatrick McHardy {
141230f33e6dSPatrick McHardy 	enum ip_conntrack_info ctinfo;
141330f33e6dSPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
14143b6b9fabSPatrick McHardy 	unsigned int matchoff, matchlen, matchend;
14153b6b9fabSPatrick McHardy 	unsigned int code, cseq, i;
141630f33e6dSPatrick McHardy 
141730f33e6dSPatrick McHardy 	if (*datalen < strlen("SIP/2.0 200"))
141830f33e6dSPatrick McHardy 		return NF_ACCEPT;
141930f33e6dSPatrick McHardy 	code = simple_strtoul(*dptr + strlen("SIP/2.0 "), NULL, 10);
1420b20ab9ccSPablo Neira Ayuso 	if (!code) {
1421b20ab9ccSPablo Neira Ayuso 		nf_ct_helper_log(skb, ct, "cannot get code");
142230f33e6dSPatrick McHardy 		return NF_DROP;
1423b20ab9ccSPablo Neira Ayuso 	}
142430f33e6dSPatrick McHardy 
142530f33e6dSPatrick McHardy 	if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
1426b20ab9ccSPablo Neira Ayuso 			      &matchoff, &matchlen) <= 0) {
1427b20ab9ccSPablo Neira Ayuso 		nf_ct_helper_log(skb, ct, "cannot parse cseq");
142830f33e6dSPatrick McHardy 		return NF_DROP;
1429b20ab9ccSPablo Neira Ayuso 	}
143030f33e6dSPatrick McHardy 	cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
14310d35d081SChristophe Leroy 	if (!cseq && *(*dptr + matchoff) != '0') {
1432b20ab9ccSPablo Neira Ayuso 		nf_ct_helper_log(skb, ct, "cannot get cseq");
143330f33e6dSPatrick McHardy 		return NF_DROP;
1434b20ab9ccSPablo Neira Ayuso 	}
14353b6b9fabSPatrick McHardy 	matchend = matchoff + matchlen + 1;
143630f33e6dSPatrick McHardy 
143730f33e6dSPatrick McHardy 	for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
143866bf7918SAlexey Dobriyan 		const struct sip_handler *handler;
143966bf7918SAlexey Dobriyan 
144030f33e6dSPatrick McHardy 		handler = &sip_handlers[i];
144130f33e6dSPatrick McHardy 		if (handler->response == NULL)
144230f33e6dSPatrick McHardy 			continue;
14433b6b9fabSPatrick McHardy 		if (*datalen < matchend + handler->len ||
144418082746SRasmus Villemoes 		    strncasecmp(*dptr + matchend, handler->method, handler->len))
144530f33e6dSPatrick McHardy 			continue;
1446051966c0SPatrick McHardy 		return handler->response(skb, protoff, dataoff, dptr, datalen,
14473b6b9fabSPatrick McHardy 					 cseq, code);
144830f33e6dSPatrick McHardy 	}
144930f33e6dSPatrick McHardy 	return NF_ACCEPT;
145030f33e6dSPatrick McHardy }
145130f33e6dSPatrick McHardy 
process_sip_request(struct sk_buff * skb,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen)1452051966c0SPatrick McHardy static int process_sip_request(struct sk_buff *skb, unsigned int protoff,
1453051966c0SPatrick McHardy 			       unsigned int dataoff,
145430f33e6dSPatrick McHardy 			       const char **dptr, unsigned int *datalen)
145530f33e6dSPatrick McHardy {
145630f33e6dSPatrick McHardy 	enum ip_conntrack_info ctinfo;
145730f33e6dSPatrick McHardy 	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
14587266507dSKevin Cernekee 	struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
14597266507dSKevin Cernekee 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
146030f33e6dSPatrick McHardy 	unsigned int matchoff, matchlen;
146130f33e6dSPatrick McHardy 	unsigned int cseq, i;
14627266507dSKevin Cernekee 	union nf_inet_addr addr;
14637266507dSKevin Cernekee 	__be16 port;
14647266507dSKevin Cernekee 
14657266507dSKevin Cernekee 	/* Many Cisco IP phones use a high source port for SIP requests, but
14667266507dSKevin Cernekee 	 * listen for the response on port 5060.  If we are the local
14677266507dSKevin Cernekee 	 * router for one of these phones, save the port number from the
14687266507dSKevin Cernekee 	 * Via: header so that nf_nat_sip can redirect the responses to
14697266507dSKevin Cernekee 	 * the correct port.
14707266507dSKevin Cernekee 	 */
14717266507dSKevin Cernekee 	if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen,
14727266507dSKevin Cernekee 				    SIP_HDR_VIA_UDP, NULL, &matchoff,
14737266507dSKevin Cernekee 				    &matchlen, &addr, &port) > 0 &&
14747266507dSKevin Cernekee 	    port != ct->tuplehash[dir].tuple.src.u.udp.port &&
14757266507dSKevin Cernekee 	    nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.src.u3))
14767266507dSKevin Cernekee 		ct_sip_info->forced_dport = port;
147730f33e6dSPatrick McHardy 
147830f33e6dSPatrick McHardy 	for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
147966bf7918SAlexey Dobriyan 		const struct sip_handler *handler;
148066bf7918SAlexey Dobriyan 
148130f33e6dSPatrick McHardy 		handler = &sip_handlers[i];
148230f33e6dSPatrick McHardy 		if (handler->request == NULL)
148330f33e6dSPatrick McHardy 			continue;
1484444f9017SUlrich Weber 		if (*datalen < handler->len + 2 ||
148518082746SRasmus Villemoes 		    strncasecmp(*dptr, handler->method, handler->len))
148630f33e6dSPatrick McHardy 			continue;
1487444f9017SUlrich Weber 		if ((*dptr)[handler->len] != ' ' ||
1488444f9017SUlrich Weber 		    !isalpha((*dptr)[handler->len+1]))
1489444f9017SUlrich Weber 			continue;
149030f33e6dSPatrick McHardy 
149130f33e6dSPatrick McHardy 		if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
1492b20ab9ccSPablo Neira Ayuso 				      &matchoff, &matchlen) <= 0) {
1493b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "cannot parse cseq");
149430f33e6dSPatrick McHardy 			return NF_DROP;
1495b20ab9ccSPablo Neira Ayuso 		}
149630f33e6dSPatrick McHardy 		cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
14970d35d081SChristophe Leroy 		if (!cseq && *(*dptr + matchoff) != '0') {
1498b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "cannot get cseq");
149930f33e6dSPatrick McHardy 			return NF_DROP;
1500b20ab9ccSPablo Neira Ayuso 		}
150130f33e6dSPatrick McHardy 
1502051966c0SPatrick McHardy 		return handler->request(skb, protoff, dataoff, dptr, datalen,
1503051966c0SPatrick McHardy 					cseq);
150430f33e6dSPatrick McHardy 	}
150530f33e6dSPatrick McHardy 	return NF_ACCEPT;
150630f33e6dSPatrick McHardy }
15077d3dd043SPatrick McHardy 
process_sip_msg(struct sk_buff * skb,struct nf_conn * ct,unsigned int protoff,unsigned int dataoff,const char ** dptr,unsigned int * datalen)1508f5b321bdSPatrick McHardy static int process_sip_msg(struct sk_buff *skb, struct nf_conn *ct,
1509051966c0SPatrick McHardy 			   unsigned int protoff, unsigned int dataoff,
1510051966c0SPatrick McHardy 			   const char **dptr, unsigned int *datalen)
1511f5b321bdSPatrick McHardy {
1512180cf72fSholger@eitzenberger.org 	const struct nf_nat_sip_hooks *hooks;
1513f5b321bdSPatrick McHardy 	int ret;
1514f5b321bdSPatrick McHardy 
151518082746SRasmus Villemoes 	if (strncasecmp(*dptr, "SIP/2.0 ", strlen("SIP/2.0 ")) != 0)
1516051966c0SPatrick McHardy 		ret = process_sip_request(skb, protoff, dataoff, dptr, datalen);
1517f5b321bdSPatrick McHardy 	else
1518051966c0SPatrick McHardy 		ret = process_sip_response(skb, protoff, dataoff, dptr, datalen);
1519f5b321bdSPatrick McHardy 
15209a664821SPatrick McHardy 	if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
1521180cf72fSholger@eitzenberger.org 		hooks = rcu_dereference(nf_nat_sip_hooks);
1522180cf72fSholger@eitzenberger.org 		if (hooks && !hooks->msg(skb, protoff, dataoff,
1523b20ab9ccSPablo Neira Ayuso 					 dptr, datalen)) {
1524b20ab9ccSPablo Neira Ayuso 			nf_ct_helper_log(skb, ct, "cannot NAT SIP message");
1525f5b321bdSPatrick McHardy 			ret = NF_DROP;
1526f5b321bdSPatrick McHardy 		}
1527b20ab9ccSPablo Neira Ayuso 	}
1528f5b321bdSPatrick McHardy 
1529f5b321bdSPatrick McHardy 	return ret;
1530f5b321bdSPatrick McHardy }
1531f5b321bdSPatrick McHardy 
sip_help_tcp(struct sk_buff * skb,unsigned int protoff,struct nf_conn * ct,enum ip_conntrack_info ctinfo)1532f5b321bdSPatrick McHardy static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
1533f5b321bdSPatrick McHardy 			struct nf_conn *ct, enum ip_conntrack_info ctinfo)
1534f5b321bdSPatrick McHardy {
1535f5b321bdSPatrick McHardy 	struct tcphdr *th, _tcph;
1536f5b321bdSPatrick McHardy 	unsigned int dataoff, datalen;
1537f5b321bdSPatrick McHardy 	unsigned int matchoff, matchlen, clen;
1538f5b321bdSPatrick McHardy 	unsigned int msglen, origlen;
1539f5b321bdSPatrick McHardy 	const char *dptr, *end;
1540f5b321bdSPatrick McHardy 	s16 diff, tdiff = 0;
15417874896aSSimon Horman 	int ret = NF_ACCEPT;
1542e6e4d9edSPatrick McHardy 	bool term;
1543f5b321bdSPatrick McHardy 
1544f5b321bdSPatrick McHardy 	if (ctinfo != IP_CT_ESTABLISHED &&
1545fb048833SEric Dumazet 	    ctinfo != IP_CT_ESTABLISHED_REPLY)
1546f5b321bdSPatrick McHardy 		return NF_ACCEPT;
1547f5b321bdSPatrick McHardy 
1548f5b321bdSPatrick McHardy 	/* No Data ? */
1549f5b321bdSPatrick McHardy 	th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
1550f5b321bdSPatrick McHardy 	if (th == NULL)
1551f5b321bdSPatrick McHardy 		return NF_ACCEPT;
1552f5b321bdSPatrick McHardy 	dataoff = protoff + th->doff * 4;
1553f5b321bdSPatrick McHardy 	if (dataoff >= skb->len)
1554f5b321bdSPatrick McHardy 		return NF_ACCEPT;
1555f5b321bdSPatrick McHardy 
1556f5b321bdSPatrick McHardy 	nf_ct_refresh(ct, skb, sip_timeout * HZ);
1557f5b321bdSPatrick McHardy 
1558a1d7c1b4SPatrick McHardy 	if (unlikely(skb_linearize(skb)))
1559a1d7c1b4SPatrick McHardy 		return NF_DROP;
1560f5b321bdSPatrick McHardy 
1561f5b321bdSPatrick McHardy 	dptr = skb->data + dataoff;
1562f5b321bdSPatrick McHardy 	datalen = skb->len - dataoff;
1563f5b321bdSPatrick McHardy 	if (datalen < strlen("SIP/2.0 200"))
1564f5b321bdSPatrick McHardy 		return NF_ACCEPT;
1565f5b321bdSPatrick McHardy 
1566f5b321bdSPatrick McHardy 	while (1) {
1567f5b321bdSPatrick McHardy 		if (ct_sip_get_header(ct, dptr, 0, datalen,
1568f5b321bdSPatrick McHardy 				      SIP_HDR_CONTENT_LENGTH,
1569f5b321bdSPatrick McHardy 				      &matchoff, &matchlen) <= 0)
1570f5b321bdSPatrick McHardy 			break;
1571f5b321bdSPatrick McHardy 
1572f5b321bdSPatrick McHardy 		clen = simple_strtoul(dptr + matchoff, (char **)&end, 10);
1573f5b321bdSPatrick McHardy 		if (dptr + matchoff == end)
1574f5b321bdSPatrick McHardy 			break;
1575f5b321bdSPatrick McHardy 
1576e6e4d9edSPatrick McHardy 		term = false;
1577e6e4d9edSPatrick McHardy 		for (; end + strlen("\r\n\r\n") <= dptr + datalen; end++) {
1578e6e4d9edSPatrick McHardy 			if (end[0] == '\r' && end[1] == '\n' &&
1579e6e4d9edSPatrick McHardy 			    end[2] == '\r' && end[3] == '\n') {
1580e6e4d9edSPatrick McHardy 				term = true;
1581f5b321bdSPatrick McHardy 				break;
1582e6e4d9edSPatrick McHardy 			}
1583e6e4d9edSPatrick McHardy 		}
1584e6e4d9edSPatrick McHardy 		if (!term)
1585f5b321bdSPatrick McHardy 			break;
1586f5b321bdSPatrick McHardy 		end += strlen("\r\n\r\n") + clen;
1587f5b321bdSPatrick McHardy 
1588f5b321bdSPatrick McHardy 		msglen = origlen = end - dptr;
15893a7b21eaSPatrick McHardy 		if (msglen > datalen)
15903a7b21eaSPatrick McHardy 			return NF_ACCEPT;
1591f5b321bdSPatrick McHardy 
1592051966c0SPatrick McHardy 		ret = process_sip_msg(skb, ct, protoff, dataoff,
1593051966c0SPatrick McHardy 				      &dptr, &msglen);
1594b20ab9ccSPablo Neira Ayuso 		/* process_sip_* functions report why this packet is dropped */
1595f5b321bdSPatrick McHardy 		if (ret != NF_ACCEPT)
1596f5b321bdSPatrick McHardy 			break;
1597f5b321bdSPatrick McHardy 		diff     = msglen - origlen;
1598f5b321bdSPatrick McHardy 		tdiff   += diff;
1599f5b321bdSPatrick McHardy 
1600f5b321bdSPatrick McHardy 		dataoff += msglen;
1601f5b321bdSPatrick McHardy 		dptr    += msglen;
1602f5b321bdSPatrick McHardy 		datalen  = datalen + diff - msglen;
1603f5b321bdSPatrick McHardy 	}
1604f5b321bdSPatrick McHardy 
16059a664821SPatrick McHardy 	if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
1606180cf72fSholger@eitzenberger.org 		const struct nf_nat_sip_hooks *hooks;
1607180cf72fSholger@eitzenberger.org 
1608180cf72fSholger@eitzenberger.org 		hooks = rcu_dereference(nf_nat_sip_hooks);
1609180cf72fSholger@eitzenberger.org 		if (hooks)
1610180cf72fSholger@eitzenberger.org 			hooks->seq_adjust(skb, protoff, tdiff);
161148f8ac26SPatrick McHardy 	}
161248f8ac26SPatrick McHardy 
1613f5b321bdSPatrick McHardy 	return ret;
1614f5b321bdSPatrick McHardy }
1615f5b321bdSPatrick McHardy 
sip_help_udp(struct sk_buff * skb,unsigned int protoff,struct nf_conn * ct,enum ip_conntrack_info ctinfo)1616f5b321bdSPatrick McHardy static int sip_help_udp(struct sk_buff *skb, unsigned int protoff,
1617f5b321bdSPatrick McHardy 			struct nf_conn *ct, enum ip_conntrack_info ctinfo)
16189fafcd7bSPatrick McHardy {
16199fafcd7bSPatrick McHardy 	unsigned int dataoff, datalen;
16209fafcd7bSPatrick McHardy 	const char *dptr;
16219fafcd7bSPatrick McHardy 
16229fafcd7bSPatrick McHardy 	/* No Data ? */
16239fafcd7bSPatrick McHardy 	dataoff = protoff + sizeof(struct udphdr);
16243db05feaSHerbert Xu 	if (dataoff >= skb->len)
16259fafcd7bSPatrick McHardy 		return NF_ACCEPT;
16269fafcd7bSPatrick McHardy 
16273db05feaSHerbert Xu 	nf_ct_refresh(ct, skb, sip_timeout * HZ);
16289fafcd7bSPatrick McHardy 
1629a1d7c1b4SPatrick McHardy 	if (unlikely(skb_linearize(skb)))
1630a1d7c1b4SPatrick McHardy 		return NF_DROP;
16319fafcd7bSPatrick McHardy 
1632f5b321bdSPatrick McHardy 	dptr = skb->data + dataoff;
16333db05feaSHerbert Xu 	datalen = skb->len - dataoff;
1634779382ebSPatrick McHardy 	if (datalen < strlen("SIP/2.0 200"))
16357d3dd043SPatrick McHardy 		return NF_ACCEPT;
16369fafcd7bSPatrick McHardy 
1637051966c0SPatrick McHardy 	return process_sip_msg(skb, ct, protoff, dataoff, &dptr, &datalen);
163833cb1e9aSPatrick McHardy }
163933cb1e9aSPatrick McHardy 
164082de0be6SGao Feng static struct nf_conntrack_helper sip[MAX_PORTS * 4] __read_mostly;
16419fafcd7bSPatrick McHardy 
16420f32a40fSPatrick McHardy static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1] = {
16430f32a40fSPatrick McHardy 	[SIP_EXPECT_SIGNALLING] = {
1644b87921bdSPatrick McHardy 		.name		= "signalling",
16450f32a40fSPatrick McHardy 		.max_expected	= 1,
16466002f266SPatrick McHardy 		.timeout	= 3 * 60,
16470f32a40fSPatrick McHardy 	},
16480f32a40fSPatrick McHardy 	[SIP_EXPECT_AUDIO] = {
1649b87921bdSPatrick McHardy 		.name		= "audio",
1650a9c1d359SPatrick McHardy 		.max_expected	= 2 * IP_CT_DIR_MAX,
16510f32a40fSPatrick McHardy 		.timeout	= 3 * 60,
16520f32a40fSPatrick McHardy 	},
16530d0ab037SPatrick McHardy 	[SIP_EXPECT_VIDEO] = {
1654b87921bdSPatrick McHardy 		.name		= "video",
16550d0ab037SPatrick McHardy 		.max_expected	= 2 * IP_CT_DIR_MAX,
16560d0ab037SPatrick McHardy 		.timeout	= 3 * 60,
16570d0ab037SPatrick McHardy 	},
16589d288dffSPatrick McHardy 	[SIP_EXPECT_IMAGE] = {
16599d288dffSPatrick McHardy 		.name		= "image",
16609d288dffSPatrick McHardy 		.max_expected	= IP_CT_DIR_MAX,
16619d288dffSPatrick McHardy 		.timeout	= 3 * 60,
16629d288dffSPatrick McHardy 	},
16636002f266SPatrick McHardy };
16646002f266SPatrick McHardy 
nf_conntrack_sip_fini(void)166535341a61STaehee Yoo static void __exit nf_conntrack_sip_fini(void)
16669fafcd7bSPatrick McHardy {
166782de0be6SGao Feng 	nf_conntrack_helpers_unregister(sip, ports_c * 4);
16689fafcd7bSPatrick McHardy }
16699fafcd7bSPatrick McHardy 
nf_conntrack_sip_init(void)16709fafcd7bSPatrick McHardy static int __init nf_conntrack_sip_init(void)
16719fafcd7bSPatrick McHardy {
167282de0be6SGao Feng 	int i, ret;
16739fafcd7bSPatrick McHardy 
1674dcf67740SFlorian Westphal 	NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sip_master));
1675dcf67740SFlorian Westphal 
16769fafcd7bSPatrick McHardy 	if (ports_c == 0)
16779fafcd7bSPatrick McHardy 		ports[ports_c++] = SIP_PORT;
16789fafcd7bSPatrick McHardy 
16799fafcd7bSPatrick McHardy 	for (i = 0; i < ports_c; i++) {
168008010a21SFlavio Leitner 		nf_ct_helper_init(&sip[4 * i], AF_INET, IPPROTO_UDP,
168108010a21SFlavio Leitner 				  HELPER_NAME, SIP_PORT, ports[i], i,
168208010a21SFlavio Leitner 				  sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp,
168382de0be6SGao Feng 				  NULL, THIS_MODULE);
168408010a21SFlavio Leitner 		nf_ct_helper_init(&sip[4 * i + 1], AF_INET, IPPROTO_TCP,
168508010a21SFlavio Leitner 				  HELPER_NAME, SIP_PORT, ports[i], i,
168608010a21SFlavio Leitner 				  sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp,
168782de0be6SGao Feng 				  NULL, THIS_MODULE);
168808010a21SFlavio Leitner 		nf_ct_helper_init(&sip[4 * i + 2], AF_INET6, IPPROTO_UDP,
168908010a21SFlavio Leitner 				  HELPER_NAME, SIP_PORT, ports[i], i,
169008010a21SFlavio Leitner 				  sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp,
169182de0be6SGao Feng 				  NULL, THIS_MODULE);
169208010a21SFlavio Leitner 		nf_ct_helper_init(&sip[4 * i + 3], AF_INET6, IPPROTO_TCP,
169308010a21SFlavio Leitner 				  HELPER_NAME, SIP_PORT, ports[i], i,
169408010a21SFlavio Leitner 				  sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp,
169582de0be6SGao Feng 				  NULL, THIS_MODULE);
169682de0be6SGao Feng 	}
1697f5b321bdSPatrick McHardy 
169882de0be6SGao Feng 	ret = nf_conntrack_helpers_register(sip, ports_c * 4);
169982de0be6SGao Feng 	if (ret < 0) {
170082de0be6SGao Feng 		pr_err("failed to register helpers\n");
17019fafcd7bSPatrick McHardy 		return ret;
17029fafcd7bSPatrick McHardy 	}
17039fafcd7bSPatrick McHardy 	return 0;
17049fafcd7bSPatrick McHardy }
17059fafcd7bSPatrick McHardy 
17069fafcd7bSPatrick McHardy module_init(nf_conntrack_sip_init);
17079fafcd7bSPatrick McHardy module_exit(nf_conntrack_sip_fini);
1708