Release 4.11 net/netfilter/nf_conntrack_sip.c
/* SIP extension for IP connection tracking.
*
* (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
* based on RR's ip_conntrack_ftp.c and other modules.
* (C) 2007 United Security Providers
* (C) 2007, 2008 Patrick McHardy <kaber@trash.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_expect.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_zones.h>
#include <linux/netfilter/nf_conntrack_sip.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
MODULE_DESCRIPTION("SIP connection tracking helper");
MODULE_ALIAS("ip_conntrack_sip");
MODULE_ALIAS_NFCT_HELPER("sip");
#define MAX_PORTS 8
static unsigned short ports[MAX_PORTS];
static unsigned int ports_c;
module_param_array(ports, ushort, &ports_c, 0400);
MODULE_PARM_DESC(ports, "port numbers of SIP servers");
static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT;
module_param(sip_timeout, uint, 0600);
MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session");
static int sip_direct_signalling __read_mostly = 1;
module_param(sip_direct_signalling, int, 0600);
MODULE_PARM_DESC(sip_direct_signalling, "expect incoming calls from registrar "
"only (default 1)");
static int sip_direct_media __read_mostly = 1;
module_param(sip_direct_media, int, 0600);
MODULE_PARM_DESC(sip_direct_media, "Expect Media streams between signalling "
"endpoints only (default 1)");
const struct nf_nat_sip_hooks *nf_nat_sip_hooks;
EXPORT_SYMBOL_GPL(nf_nat_sip_hooks);
static int string_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
int len = 0;
while (dptr < limit && isalpha(*dptr)) {
dptr++;
len++;
}
return len;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 53 | 98.15% | 2 | 66.67% |
Jan Engelhardt | 1 | 1.85% | 1 | 33.33% |
Total | 54 | 100.00% | 3 | 100.00% |
static int digits_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
int len = 0;
while (dptr < limit && isdigit(*dptr)) {
dptr++;
len++;
}
return len;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 53 | 98.15% | 2 | 66.67% |
Jan Engelhardt | 1 | 1.85% | 1 | 33.33% |
Total | 54 | 100.00% | 3 | 100.00% |
static int iswordc(const char c)
{
if (isalnum(c) || c == '!' || c == '"' || c == '%' ||
(c >= '(' && c <= '+') || c == ':' || c == '<' || c == '>' ||
c == '?' || (c >= '[' && c <= ']') || c == '_' || c == '`' ||
c == '{' || c == '}' || c == '~' || (c >= '-' && c <= '/') ||
c == '\'')
return 1;
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Simon Horman | 90 | 85.71% | 1 | 50.00% |
Marco Angaroni | 15 | 14.29% | 1 | 50.00% |
Total | 105 | 100.00% | 2 | 100.00% |
static int word_len(const char *dptr, const char *limit)
{
int len = 0;
while (dptr < limit && iswordc(*dptr)) {
dptr++;
len++;
}
return len;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Simon Horman | 44 | 100.00% | 1 | 100.00% |
Total | 44 | 100.00% | 1 | 100.00% |
static int callid_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
int len, domain_len;
len = word_len(dptr, limit);
dptr += len;
if (!len || dptr == limit || *dptr != '@')
return len;
dptr++;
len++;
domain_len = word_len(dptr, limit);
if (!domain_len)
return 0;
return len + domain_len;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Simon Horman | 89 | 100.00% | 1 | 100.00% |
Total | 89 | 100.00% | 1 | 100.00% |
/* get media type + port length */
static int media_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
int len = string_len(ct, dptr, limit, shift);
dptr += len;
if (dptr >= limit || *dptr != ' ')
return 0;
len++;
dptr++;
return len + digits_len(ct, dptr, limit, shift);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 78 | 100.00% | 1 | 100.00% |
Total | 78 | 100.00% | 1 | 100.00% |
static int sip_parse_addr(const struct nf_conn *ct, const char *cp,
const char **endp, union nf_inet_addr *addr,
const char *limit, bool delim)
{
const char *end;
int ret;
if (!ct)
return 0;
memset(addr, 0, sizeof(*addr));
switch (nf_ct_l3num(ct)) {
case AF_INET:
ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
if (ret == 0)
return 0;
break;
case AF_INET6:
if (cp < limit && *cp == '[')
cp++;
else if (delim)
return 0;
ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
if (ret == 0)
return 0;
if (end < limit && *end == ']')
end++;
else if (delim)
return 0;
break;
default:
BUG();
}
if (endp)
*endp = end;
return 1;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 203 | 95.75% | 5 | 71.43% |
Simon Horman | 8 | 3.77% | 1 | 14.29% |
Jan Engelhardt | 1 | 0.47% | 1 | 14.29% |
Total | 212 | 100.00% | 7 | 100.00% |
/* skip ip address. returns its length. */
static int epaddr_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
union nf_inet_addr addr;
const char *aux = dptr;
if (!sip_parse_addr(ct, dptr, &dptr, &addr, limit, true)) {
pr_debug("ip: %s parse failed.!\n", dptr);
return 0;
}
/* Port number */
if (*dptr == ':') {
dptr++;
dptr += digits_len(ct, dptr, limit, shift);
}
return dptr - aux;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 99 | 99.00% | 4 | 80.00% |
Jan Engelhardt | 1 | 1.00% | 1 | 20.00% |
Total | 100 | 100.00% | 5 | 100.00% |
/* get address length, skiping user info. */
static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
const char *start = dptr;
int s = *shift;
/* Search for @, but stop at the end of the line.
* We are inside a sip: URI, so we don't need to worry about
* continuation lines. */
while (dptr < limit &&
*dptr != '@' && *dptr != '\r' && *dptr != '\n') {
(*shift)++;
dptr++;
}
if (dptr < limit && *dptr == '@') {
dptr++;
(*shift)++;
} else {
dptr = start;
*shift = s;
}
return epaddr_len(ct, dptr, limit, shift);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 117 | 99.15% | 3 | 75.00% |
Jan Engelhardt | 1 | 0.85% | 1 | 25.00% |
Total | 118 | 100.00% | 4 | 100.00% |
/* Parse a SIP request line of the form:
*
* Request-Line = Method SP Request-URI SP SIP-Version CRLF
*
* and return the offset and length of the address contained in the Request-URI.
*/
int ct_sip_parse_request(const struct nf_conn *ct,
const char *dptr, unsigned int datalen,
unsigned int *matchoff, unsigned int *matchlen,
union nf_inet_addr *addr, __be16 *port)
{
const char *start = dptr, *limit = dptr + datalen, *end;
unsigned int mlen;
unsigned int p;
int shift = 0;
/* Skip method and following whitespace */
mlen = string_len(ct, dptr, limit, NULL);
if (!mlen)
return 0;
dptr += mlen;
if (++dptr >= limit)
return 0;
/* Find SIP URI */
for (; dptr < limit - strlen("sip:"); dptr++) {
if (*dptr == '\r' || *dptr == '\n')
return -1;
if (strncasecmp(dptr, "sip:", strlen("sip:")) == 0) {
dptr += strlen("sip:");
break;
}
}
if (!skp_epaddr_len(ct, dptr, limit, &shift))
return 0;
dptr += shift;
if (!sip_parse_addr(ct, dptr, &end, addr, limit, true))
return -1;
if (end < limit && *end == ':') {
end++;
p = simple_strtoul(end, (char **)&end, 10);
if (p < 1024 || p > 65535)
return -1;
*port = htons(p);
} else
*port = htons(SIP_PORT);
if (end == dptr)
return 0;
*matchoff = dptr - start;
*matchlen = end - dptr;
return 1;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 298 | 99.33% | 6 | 75.00% |
Jan Engelhardt | 1 | 0.33% | 1 | 12.50% |
Rasmus Villemoes | 1 | 0.33% | 1 | 12.50% |
Total | 300 | 100.00% | 8 | 100.00% |
EXPORT_SYMBOL_GPL(ct_sip_parse_request);
/* SIP header parsing: SIP headers are located at the beginning of a line, but
* may span several lines, in which case the continuation lines begin with a
* whitespace character. RFC 2543 allows lines to be terminated with CR, LF or
* CRLF, RFC 3261 allows only CRLF, we support both.
*
* Headers are followed by (optionally) whitespace, a colon, again (optionally)
* whitespace and the values. Whitespace in this context means any amount of
* tabs, spaces and continuation lines, which are treated as a single whitespace
* character.
*
* Some headers may appear multiple times. A comma separated list of values is
* equivalent to multiple headers.
*/
static const struct sip_header ct_sip_hdrs[] = {
[SIP_HDR_CSEQ] = SIP_HDR("CSeq", NULL, NULL, digits_len),
[SIP_HDR_FROM] = SIP_HDR("From", "f", "sip:", skp_epaddr_len),
[SIP_HDR_TO] = SIP_HDR("To", "t", "sip:", skp_epaddr_len),
[SIP_HDR_CONTACT] = SIP_HDR("Contact", "m", "sip:", skp_epaddr_len),
[SIP_HDR_VIA_UDP] = SIP_HDR("Via", "v", "UDP ", epaddr_len),
[SIP_HDR_VIA_TCP] = SIP_HDR("Via", "v", "TCP ", epaddr_len),
[SIP_HDR_EXPIRES] = SIP_HDR("Expires", NULL, NULL, digits_len),
[SIP_HDR_CONTENT_LENGTH] = SIP_HDR("Content-Length", "l", NULL, digits_len),
[SIP_HDR_CALL_ID] = SIP_HDR("Call-Id", "i", NULL, callid_len),
};
static const char *sip_follow_continuation(const char *dptr, const char *limit)
{
/* Walk past newline */
if (++dptr >= limit)
return NULL;
/* Skip '\n' in CR LF */
if (*(dptr - 1) == '\r' && *dptr == '\n') {
if (++dptr >= limit)
return NULL;
}
/* Continuation line? */
if (*dptr != ' ' && *dptr != '\t')
return NULL;
/* skip leading whitespace */
for (; dptr < limit; dptr++) {
if (*dptr != ' ' && *dptr != '\t')
break;
}
return dptr;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 103 | 100.00% | 2 | 100.00% |
Total | 103 | 100.00% | 2 | 100.00% |
static const char *sip_skip_whitespace(const char *dptr, const char *limit)
{
for (; dptr < limit; dptr++) {
if (*dptr == ' ' || *dptr == '\t')
continue;
if (*dptr != '\r' && *dptr != '\n')
break;
dptr = sip_follow_continuation(dptr, limit);
break;
}
return dptr;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 63 | 91.30% | 3 | 60.00% |
Marco Angaroni | 6 | 8.70% | 2 | 40.00% |
Total | 69 | 100.00% | 5 | 100.00% |
/* Search within a SIP header value, dealing with continuation lines */
static const char *ct_sip_header_search(const char *dptr, const char *limit,
const char *needle, unsigned int len)
{
for (limit -= len; dptr < limit; dptr++) {
if (*dptr == '\r' || *dptr == '\n') {
dptr = sip_follow_continuation(dptr, limit);
if (dptr == NULL)
break;
continue;
}
if (strncasecmp(dptr, needle, len) == 0)
return dptr;
}
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 91 | 98.91% | 2 | 66.67% |
Rasmus Villemoes | 1 | 1.09% | 1 | 33.33% |
Total | 92 | 100.00% | 3 | 100.00% |
int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type,
unsigned int *matchoff, unsigned int *matchlen)
{
const struct sip_header *hdr = &ct_sip_hdrs[type];
const char *start = dptr, *limit = dptr + datalen;
int shift = 0;
for (dptr += dataoff; dptr < limit; dptr++) {
/* Find beginning of line */
if (*dptr != '\r' && *dptr != '\n')
continue;
if (++dptr >= limit)
break;
if (*(dptr - 1) == '\r' && *dptr == '\n') {
if (++dptr >= limit)
break;
}
/* Skip continuation lines */
if (*dptr == ' ' || *dptr == '\t')
continue;
/* Find header. Compact headers must be followed by a
* non-alphabetic character to avoid mismatches. */
if (limit - dptr >= hdr->len &&
strncasecmp(dptr, hdr->name, hdr->len) == 0)
dptr += hdr->len;
else if (hdr->cname && limit - dptr >= hdr->clen + 1 &&
strncasecmp(dptr, hdr->cname, hdr->clen) == 0 &&
!isalpha(*(dptr + hdr->clen)))
dptr += hdr->clen;
else
continue;
/* Find and skip colon */
dptr = sip_skip_whitespace(dptr, limit);
if (dptr == NULL)
break;
if (*dptr != ':' || ++dptr >= limit)
break;
/* Skip whitespace after colon */
dptr = sip_skip_whitespace(dptr, limit);
if (dptr == NULL)
break;
*matchoff = dptr - start;
if (hdr->search) {
dptr = ct_sip_header_search(dptr, limit, hdr->search,
hdr->slen);
if (!dptr)
return -1;
dptr += hdr->slen;
}
*matchlen = hdr->match_len(ct, dptr, limit, &shift);
if (!*matchlen)
return -1;
*matchoff = dptr - start + shift;
return 1;
}
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 364 | 99.18% | 3 | 60.00% |
Rasmus Villemoes | 2 | 0.54% | 1 | 20.00% |
Jan Engelhardt | 1 | 0.27% | 1 | 20.00% |
Total | 367 | 100.00% | 5 | 100.00% |
EXPORT_SYMBOL_GPL(ct_sip_get_header);
/* Get next header field in a list of comma separated values */
static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type,
unsigned int *matchoff, unsigned int *matchlen)
{
const struct sip_header *hdr = &ct_sip_hdrs[type];
const char *start = dptr, *limit = dptr + datalen;
int shift = 0;
dptr += dataoff;
dptr = ct_sip_header_search(dptr, limit, ",", strlen(","));
if (!dptr)
return 0;
dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen);
if (!dptr)
return 0;
dptr += hdr->slen;
*matchoff = dptr - start;
*matchlen = hdr->match_len(ct, dptr, limit, &shift);
if (!*matchlen)
return -1;
*matchoff += shift;
return 1;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 171 | 100.00% | 1 | 100.00% |
Total | 171 | 100.00% | 1 | 100.00% |
/* Walk through headers until a parsable one is found or no header of the
* given type is left. */
static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type, int *in_header,
unsigned int *matchoff, unsigned int *matchlen)
{
int ret;
if (in_header && *in_header) {
while (1) {
ret = ct_sip_next_header(ct, dptr, dataoff, datalen,
type, matchoff, matchlen);
if (ret > 0)
return ret;
if (ret == 0)
break;
dataoff += *matchoff;
}
*in_header = 0;
}
while (1) {
ret = ct_sip_get_header(ct, dptr, dataoff, datalen,
type, matchoff, matchlen);
if (ret > 0)
break;
if (ret == 0)
return ret;
dataoff += *matchoff;
}
if (in_header)
*in_header = 1;
return 1;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 164 | 100.00% | 1 | 100.00% |
Total | 164 | 100.00% | 1 | 100.00% |
/* Locate a SIP header, parse the URI and return the offset and length of
* the address as well as the address and port themselves. A stream of
* headers can be parsed by handing in a non-NULL datalen and in_header
* pointer.
*/
int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr,
unsigned int *dataoff, unsigned int datalen,
enum sip_header_types type, int *in_header,
unsigned int *matchoff, unsigned int *matchlen,
union nf_inet_addr *addr, __be16 *port)
{
const char *c, *limit = dptr + datalen;
unsigned int p;
int ret;
ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen,
type, in_header, matchoff, matchlen);
WARN_ON(ret < 0);
if (ret == 0)
return ret;
if (!sip_parse_addr(ct, dptr + *matchoff, &c, addr, limit, true))
return -1;
if (*c == ':') {
c++;
p = simple_strtoul(c, (char **)&c, 10);
if (p < 1024 || p > 65535)
return -1;
*port = htons(p);
} else
*port = htons(SIP_PORT);
if (dataoff)
*dataoff = c - dptr;
return 1;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 213 | 100.00% | 2 | 100.00% |
Total | 213 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri);
static int 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)
{
const char *limit = dptr + datalen;
const char *start;
const char *end;
limit = ct_sip_header_search(dptr + dataoff, limit, ",", strlen(","));
if (!limit)
limit = dptr + datalen;
start = ct_sip_header_search(dptr + dataoff, limit, name, strlen(name));
if (!start)
return 0;
start += strlen(name);
end = ct_sip_header_search(start, limit, ";", strlen(";"));
if (!end)
end = limit;
*matchoff = start - dptr;
*matchlen = end - start;
return 1;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Patrick McHardy | 163 | 100.00% | 1 | 100.00% |
Total | 163 | 100.00% | 1 | 100.00% |
/* Parse address from header parameter and return address, offset and length */
int 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)
{
const char *limit =