dect
/
linux-2.6
Archived
13
0
Fork 0
This repository has been archived on 2022-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
linux-2.6/net/netfilter/nf_conntrack_sip.c

713 lines
19 KiB
C
Raw Normal View History

/* SIP extension for IP connection tracking.
*
* (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
* based on RR's ip_conntrack_ftp.c and other modules.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/netfilter.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_expect.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <linux/netfilter/nf_conntrack_sip.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
MODULE_DESCRIPTION("SIP connection tracking helper");
MODULE_ALIAS("ip_conntrack_sip");
#define MAX_PORTS 8
static unsigned short ports[MAX_PORTS];
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");
unsigned int (*nf_nat_sip_hook)(struct sk_buff *skb,
const char **dptr,
unsigned int *datalen) __read_mostly;
EXPORT_SYMBOL_GPL(nf_nat_sip_hook);
unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb,
const char **dptr,
unsigned int *datalen,
struct nf_conntrack_expect *exp) __read_mostly;
EXPORT_SYMBOL_GPL(nf_nat_sdp_hook);
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;
}
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;
}
static int parse_addr(const struct nf_conn *ct, const char *cp,
const char **endp, union nf_inet_addr *addr,
const char *limit)
{
const char *end;
int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
int ret = 0;
switch (family) {
case AF_INET:
ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
break;
case AF_INET6:
ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
break;
default:
BUG();
}
if (ret == 0 || end == cp)
return 0;
if (endp)
*endp = end;
return 1;
}
/* skip ip address. returns its length. */
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 (!parse_addr(ct, dptr, &dptr, &addr, limit)) {
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;
}
/* 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);
}
/* 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 */
limit -= strlen("sip:");
for (; dptr < limit; dptr++) {
if (*dptr == '\r' || *dptr == '\n')
return -1;
if (strnicmp(dptr, "sip:", strlen("sip:")) == 0)
break;
}
if (!skp_epaddr_len(ct, dptr, limit, &shift))
return 0;
dptr += shift;
if (!parse_addr(ct, dptr, &end, addr, limit))
return -1;
if (end < limit && *end == ':') {
end++;
p = simple_strtoul(end, (char **)&end, 10);
if (p < 1024 || p > 65535)
return -1;
*port = htons(p);
} else
*port = htons(SIP_PORT);
if (end == dptr)
return 0;
*matchoff = dptr - start;
*matchlen = end - dptr;
return 1;
}
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 seperated list of values is
* equivalent to multiple headers.
*/
static const struct sip_header ct_sip_hdrs[] = {
[SIP_HDR_FROM] = SIP_HDR("From", "f", "sip:", skp_epaddr_len),
[SIP_HDR_TO] = SIP_HDR("To", "t", "sip:", skp_epaddr_len),
[SIP_HDR_CONTACT] = SIP_HDR("Contact", "m", "sip:", skp_epaddr_len),
[SIP_HDR_VIA] = SIP_HDR("Via", "v", "UDP ", epaddr_len),
[SIP_HDR_CONTENT_LENGTH] = SIP_HDR("Content-Length", "l", NULL, digits_len),
};
static const char *sip_follow_continuation(const char *dptr, const char *limit)
{
/* 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;
}
static const char *sip_skip_whitespace(const char *dptr, const char *limit)
{
for (; dptr < limit; dptr++) {
if (*dptr == ' ')
continue;
if (*dptr != '\r' && *dptr != '\n')
break;
dptr = sip_follow_continuation(dptr, limit);
if (dptr == NULL)
return NULL;
}
return dptr;
}
/* Search within a SIP header value, dealing with continuation lines */
static const char *ct_sip_header_search(const char *dptr, const char *limit,
const char *needle, unsigned int len)
{
for (limit -= len; dptr < limit; dptr++) {
if (*dptr == '\r' || *dptr == '\n') {
dptr = sip_follow_continuation(dptr, limit);
if (dptr == NULL)
break;
continue;
}
if (strnicmp(dptr, needle, len) == 0)
return dptr;
}
return NULL;
}
int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type,
unsigned int *matchoff, unsigned int *matchlen)
{
const struct sip_header *hdr = &ct_sip_hdrs[type];
const char *start = dptr, *limit = dptr + datalen;
int shift = 0;
for (dptr += dataoff; dptr < limit; dptr++) {
/* Find beginning of line */
if (*dptr != '\r' && *dptr != '\n')
continue;
if (++dptr >= limit)
break;
if (*(dptr - 1) == '\r' && *dptr == '\n') {
if (++dptr >= limit)
break;
}
/* 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 &&
strnicmp(dptr, hdr->name, hdr->len) == 0)
dptr += hdr->len;
else if (hdr->cname && limit - dptr >= hdr->clen + 1 &&
strnicmp(dptr, hdr->cname, hdr->clen) == 0 &&
!isalpha(*(dptr + hdr->clen + 1)))
dptr += hdr->clen;
else
continue;
/* 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;
}
EXPORT_SYMBOL_GPL(ct_sip_get_header);
/* Get next header field in a list of comma seperated values */
static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type,
unsigned int *matchoff, unsigned int *matchlen)
{
const struct sip_header *hdr = &ct_sip_hdrs[type];
const char *start = dptr, *limit = dptr + datalen;
int shift = 0;
dptr += dataoff;
dptr = ct_sip_header_search(dptr, limit, ",", strlen(","));
if (!dptr)
return 0;
dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen);
if (!dptr)
return 0;
dptr += hdr->slen;
*matchoff = dptr - start;
*matchlen = hdr->match_len(ct, dptr, limit, &shift);
if (!*matchlen)
return -1;
*matchoff += shift;
return 1;
}
/* Walk through headers until a parsable one is found or no header of the
* given type is left. */
static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type, int *in_header,
unsigned int *matchoff, unsigned int *matchlen)
{
int ret;
if (in_header && *in_header) {
while (1) {
ret = ct_sip_next_header(ct, dptr, dataoff, datalen,
type, matchoff, matchlen);
if (ret > 0)
return ret;
if (ret == 0)
break;
dataoff += *matchoff;
}
*in_header = 0;
}
while (1) {
ret = ct_sip_get_header(ct, dptr, dataoff, datalen,
type, matchoff, matchlen);
if (ret > 0)
break;
if (ret == 0)
return ret;
dataoff += *matchoff;
}
if (in_header)
*in_header = 1;
return 1;
}
/* Locate a SIP header, parse the URI and return the offset and length of
* the address as well as the address and port themselves. A stream of
* headers can be parsed by handing in a non-NULL datalen and in_header
* pointer.
*/
int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr,
unsigned int *dataoff, unsigned int datalen,
enum sip_header_types type, int *in_header,
unsigned int *matchoff, unsigned int *matchlen,
union nf_inet_addr *addr, __be16 *port)
{
const char *c, *limit = dptr + datalen;
unsigned int p;
int ret;
ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen,
type, in_header, matchoff, matchlen);
WARN_ON(ret < 0);
if (ret == 0)
return ret;
if (!parse_addr(ct, dptr + *matchoff, &c, addr, limit))
return -1;
if (*c == ':') {
c++;
p = simple_strtoul(c, (char **)&c, 10);
if (p < 1024 || p > 65535)
return -1;
*port = htons(p);
} else
*port = htons(SIP_PORT);
if (dataoff)
*dataoff = c - dptr;
return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri);
/* SDP header parsing: a SDP session description contains an ordered set of
* headers, starting with a section containing general session parameters,
* optionally followed by multiple media descriptions.
*
* SDP headers always start at the beginning of a line. According to RFC 2327:
* "The sequence CRLF (0x0d0a) is used to end a record, although parsers should
* be tolerant and also accept records terminated with a single newline
* character". We handle both cases.
*/
static const struct sip_header ct_sdp_hdrs[] = {
[SDP_HDR_VERSION] = SDP_HDR("v=", NULL, digits_len),
[SDP_HDR_OWNER_IP4] = SDP_HDR("o=", "IN IP4 ", epaddr_len),
[SDP_HDR_CONNECTION_IP4] = SDP_HDR("c=", "IN IP4 ", epaddr_len),
[SDP_HDR_OWNER_IP6] = SDP_HDR("o=", "IN IP6 ", epaddr_len),
[SDP_HDR_CONNECTION_IP6] = SDP_HDR("c=", "IN IP6 ", epaddr_len),
[SDP_HDR_MEDIA] = SDP_HDR("m=", "audio ", digits_len),
};
/* Linear string search within SDP header values */
static const char *ct_sdp_header_search(const char *dptr, const char *limit,
const char *needle, unsigned int len)
{
for (limit -= len; dptr < limit; dptr++) {
if (*dptr == '\r' || *dptr == '\n')
break;
if (strncmp(dptr, needle, len) == 0)
return dptr;
}
return NULL;
}
/* Locate a SDP header (optionally a substring within the header value),
* optionally stopping at the first occurence of the term header, parse
* it and return the offset and length of the data we're interested in.
*/
int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sdp_header_types type,
enum sdp_header_types term,
unsigned int *matchoff, unsigned int *matchlen)
{
const struct sip_header *hdr = &ct_sdp_hdrs[type];
const struct sip_header *thdr = &ct_sdp_hdrs[term];
const char *start = dptr, *limit = dptr + datalen;
int shift = 0;
for (dptr += dataoff; dptr < limit; dptr++) {
/* Find beginning of line */
if (*dptr != '\r' && *dptr != '\n')
continue;
if (++dptr >= limit)
break;
if (*(dptr - 1) == '\r' && *dptr == '\n') {
if (++dptr >= limit)
break;
}
if (term != SDP_HDR_UNSPEC &&
limit - dptr >= thdr->len &&
strnicmp(dptr, thdr->name, thdr->len) == 0)
break;
else if (limit - dptr >= hdr->len &&
strnicmp(dptr, hdr->name, hdr->len) == 0)
dptr += hdr->len;
else
continue;
*matchoff = dptr - start;
if (hdr->search) {
dptr = ct_sdp_header_search(dptr, limit, hdr->search,
hdr->slen);
if (!dptr)
return -1;
dptr += hdr->slen;
}
*matchlen = hdr->match_len(ct, dptr, limit, &shift);
if (!*matchlen)
return -1;
*matchoff = dptr - start + shift;
return 1;
}
return 0;
}
EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header);
static int set_expected_rtp(struct sk_buff *skb,
const char **dptr, unsigned int *datalen,
union nf_inet_addr *addr, __be16 port)
{
struct nf_conntrack_expect *exp;
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
int family = ct->tuplehash[!dir].tuple.src.l3num;
int ret;
typeof(nf_nat_sdp_hook) nf_nat_sdp;
exp = nf_ct_expect_alloc(ct);
if (exp == NULL)
return NF_DROP;
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, family,
&ct->tuplehash[!dir].tuple.src.u3, addr,
IPPROTO_UDP, NULL, &port);
nf_nat_sdp = rcu_dereference(nf_nat_sdp_hook);
if (nf_nat_sdp && ct->status & IPS_NAT_MASK)
ret = nf_nat_sdp(skb, dptr, datalen, exp);
else {
if (nf_ct_expect_related(exp) != 0)
ret = NF_DROP;
else
ret = NF_ACCEPT;
}
nf_ct_expect_put(exp);
return ret;
}
static int process_sdp(struct sk_buff *skb,
const char **dptr, unsigned int *datalen)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
unsigned int matchoff, matchlen;
union nf_inet_addr addr;
unsigned int port;
enum sdp_header_types type;
/* Get address and port from SDP packet. */
type = family == AF_INET ? SDP_HDR_CONNECTION_IP4 :
SDP_HDR_CONNECTION_IP6;
if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
type, SDP_HDR_UNSPEC,
&matchoff, &matchlen) <= 0)
return NF_ACCEPT;
/* We'll drop only if there are parse problems. */
if (!parse_addr(ct, *dptr + matchoff, NULL, &addr, *dptr + *datalen))
return NF_DROP;
if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
&matchoff, &matchlen) <= 0)
return NF_ACCEPT;
port = simple_strtoul(*dptr + matchoff, NULL, 10);
if (port < 1024 || port > 65535)
return NF_DROP;
return set_expected_rtp(skb, dptr, datalen, &addr, htons(port));
}
static int sip_help(struct sk_buff *skb,
unsigned int protoff,
struct nf_conn *ct,
enum ip_conntrack_info ctinfo)
{
unsigned int dataoff, datalen;
const char *dptr;
typeof(nf_nat_sip_hook) nf_nat_sip;
/* No Data ? */
dataoff = protoff + sizeof(struct udphdr);
if (dataoff >= skb->len)
return NF_ACCEPT;
nf_ct_refresh(ct, skb, sip_timeout * HZ);
if (!skb_is_nonlinear(skb))
dptr = skb->data + dataoff;
else {
pr_debug("Copy of skbuff not supported yet.\n");
return NF_ACCEPT;
}
nf_nat_sip = rcu_dereference(nf_nat_sip_hook);
if (nf_nat_sip && ct->status & IPS_NAT_MASK) {
if (!nf_nat_sip(skb, &dptr, &datalen))
return NF_DROP;
}
datalen = skb->len - dataoff;
if (datalen < strlen("SIP/2.0 200"))
return NF_ACCEPT;
/* RTP info only in some SDP pkts */
if (strnicmp(dptr, "INVITE", strlen("INVITE")) != 0 &&
strnicmp(dptr, "UPDATE", strlen("UPDATE")) != 0 &&
strnicmp(dptr, "SIP/2.0 180", strlen("SIP/2.0 180")) != 0 &&
strnicmp(dptr, "SIP/2.0 183", strlen("SIP/2.0 183")) != 0 &&
strnicmp(dptr, "SIP/2.0 200", strlen("SIP/2.0 200")) != 0)
return NF_ACCEPT;
return process_sdp(skb, &dptr, &datalen);
}
static struct nf_conntrack_helper sip[MAX_PORTS][2] __read_mostly;
static char sip_names[MAX_PORTS][2][sizeof("sip-65535")] __read_mostly;
static const struct nf_conntrack_expect_policy sip_exp_policy = {
.max_expected = 2,
.timeout = 3 * 60,
};
static void nf_conntrack_sip_fini(void)
{
int i, j;
for (i = 0; i < ports_c; i++) {
for (j = 0; j < 2; j++) {
if (sip[i][j].me == NULL)
continue;
nf_conntrack_helper_unregister(&sip[i][j]);
}
}
}
static int __init nf_conntrack_sip_init(void)
{
int i, j, ret;
char *tmpname;
if (ports_c == 0)
ports[ports_c++] = SIP_PORT;
for (i = 0; i < ports_c; i++) {
memset(&sip[i], 0, sizeof(sip[i]));
sip[i][0].tuple.src.l3num = AF_INET;
sip[i][1].tuple.src.l3num = AF_INET6;
for (j = 0; j < 2; j++) {
sip[i][j].tuple.dst.protonum = IPPROTO_UDP;
sip[i][j].tuple.src.u.udp.port = htons(ports[i]);
sip[i][j].expect_policy = &sip_exp_policy;
sip[i][j].me = THIS_MODULE;
sip[i][j].help = sip_help;
tmpname = &sip_names[i][j][0];
if (ports[i] == SIP_PORT)
sprintf(tmpname, "sip");
else
sprintf(tmpname, "sip-%u", i);
sip[i][j].name = tmpname;
pr_debug("port #%u: %u\n", i, ports[i]);
ret = nf_conntrack_helper_register(&sip[i][j]);
if (ret) {
printk("nf_ct_sip: failed to register helper "
"for pf: %u port: %u\n",
sip[i][j].tuple.src.l3num, ports[i]);
nf_conntrack_sip_fini();
return ret;
}
}
}
return 0;
}
module_init(nf_conntrack_sip_init);
module_exit(nf_conntrack_sip_fini);