Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
577 lines (487 sloc) 15.3 KB
/*
* RTSP extension for IP connection tracking
* (C) 2003 by Tom Marshall <tmarshall at real.com>
*
* 2005-02-13: Harald Welte <laforge at netfilter.org>
* - port to 2.6
* - update to recent post-2.6.11 api changes
* 2006-09-14: Steven Van Acker <deepstar at singularity.be>
* - removed calls to NAT code from conntrack helper: NAT no longer needed to use rtsp-conntrack
* 2007-04-18: Michael Guntsche <mike at it-loops.com>
* - Port to new NF API
* 2013-03-04: Il'inykh Sergey <sergeyi at inango-sw.com>. Inango Systems Ltd
* - fixed rtcp nat mapping and other port mapping fixes
* - simple TEARDOWN request handling
* - codestyle fixes and other less significant bug fixes
*
* based on ip_conntrack_irc.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Module load syntax:
* insmod nf_conntrack_rtsp.o ports=port1,port2,...port<MAX_PORTS>
* max_outstanding=n setup_timeout=secs
*
* If no ports are specified, the default will be port 554.
*
* With max_outstanding you can define the maximum number of not yet
* answered SETUP requests per RTSP session (default 8).
* With setup_timeout you can specify how long the system waits for
* an expected data channel (default 300 seconds).
*
*/
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/ip.h>
#include <linux/inet.h>
#include <net/tcp.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_expect.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include "nf_conntrack_rtsp.h"
#define NF_NEED_STRNCASECMP
#define NF_NEED_STRTOU16
#define NF_NEED_STRTOU32
#define NF_NEED_NEXTLINE
#include "netfilter_helpers.h"
#define NF_NEED_MIME_NEXTLINE
#include "netfilter_mime.h"
#include <linux/ctype.h>
#define MAX_PORTS 8
static int ports[MAX_PORTS];
static int num_ports = 0;
static int max_outstanding = 8;
static unsigned int setup_timeout = 300;
MODULE_AUTHOR("Tom Marshall <tmarshall at real.com>");
MODULE_DESCRIPTION("RTSP connection tracking module");
MODULE_LICENSE("GPL");
module_param_array(ports, int, &num_ports, 0400);
MODULE_PARM_DESC(ports, "port numbers of RTSP servers");
module_param(max_outstanding, int, 0400);
MODULE_PARM_DESC(max_outstanding, "max number of outstanding SETUP requests per RTSP session");
module_param(setup_timeout, int, 0400);
MODULE_PARM_DESC(setup_timeout, "timeout on for unestablished data channels");
static char *rtsp_buffer;
static DEFINE_SPINLOCK(rtsp_buffer_lock);
static struct nf_conntrack_expect_policy rtsp_exp_policy;
unsigned int (*nf_nat_rtsp_hook)(struct sk_buff *skb,
enum ip_conntrack_info ctinfo,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
unsigned int protoff,
#endif
unsigned int matchoff, unsigned int matchlen,
struct ip_ct_rtsp_expect* prtspexp,
struct nf_conntrack_expect *rtp_exp,
struct nf_conntrack_expect *rtcp_exp);
EXPORT_SYMBOL_GPL(nf_nat_rtsp_hook);
/*
* Max mappings we will allow for one RTSP connection (for RTP, the number
* of allocated ports is twice this value). Note that SMIL burns a lot of
* ports so keep this reasonably high. If this is too low, you will see a
* lot of "no free client map entries" messages.
*/
#define MAX_PORT_MAPS 16
/*** default port list was here in the masq code: 554, 3030, 4040 ***/
#define SKIP_WSPACE(ptr,len,off) while(off < len && isspace(*(ptr+off))) { off++; }
/*
* Parse an RTSP packet.
*
* Returns zero if parsing failed.
*
* Parameters:
* IN ptcp tcp data pointer
* IN tcplen tcp data len
* IN/OUT ptcpoff points to current tcp offset
* OUT phdrsoff set to offset of rtsp headers
* OUT phdrslen set to length of rtsp headers
* OUT pcseqoff set to offset of CSeq header
* OUT pcseqlen set to length of CSeq header
*/
static int
rtsp_parse_message(char* ptcp, uint tcplen, uint* ptcpoff,
uint* phdrsoff, uint* phdrslen,
uint* pcseqoff, uint* pcseqlen,
uint* transoff, uint* translen)
{
uint entitylen = 0;
uint lineoff;
uint linelen;
if (!nf_nextline(ptcp, tcplen, ptcpoff, &lineoff, &linelen))
return 0;
*phdrsoff = *ptcpoff;
while (nf_mime_nextline(ptcp, tcplen, ptcpoff, &lineoff, &linelen)) {
if (linelen == 0) {
if (entitylen > 0)
*ptcpoff += min(entitylen, tcplen - *ptcpoff);
break;
}
if (lineoff+linelen > tcplen) {
pr_info("!! overrun !!\n");
break;
}
if (nf_strncasecmp(ptcp+lineoff, "CSeq:", 5) == 0) {
*pcseqoff = lineoff;
*pcseqlen = linelen;
}
if (nf_strncasecmp(ptcp+lineoff, "Transport:", 10) == 0) {
*transoff = lineoff;
*translen = linelen;
}
if (nf_strncasecmp(ptcp+lineoff, "Content-Length:", 15) == 0) {
uint off = lineoff+15;
SKIP_WSPACE(ptcp+lineoff, linelen, off);
nf_strtou32(ptcp+off, &entitylen);
}
}
*phdrslen = (*ptcpoff) - (*phdrsoff);
return 1;
}
/*
* Find lo/hi client ports (if any) in transport header
* In:
* ptcp, tcplen = packet
* tranoff, tranlen = buffer to search
*
* Out:
* pport_lo, pport_hi = lo/hi ports (host endian)
*
* Returns nonzero if any client ports found
*
* Note: it is valid (and expected) for the client to request multiple
* transports, so we need to parse the entire line.
*/
static int
rtsp_parse_transport(char* ptran, uint tranlen,
struct ip_ct_rtsp_expect* prtspexp)
{
int rc = 0;
uint off = 0;
if (tranlen < 10 || !iseol(ptran[tranlen-1]) ||
nf_strncasecmp(ptran, "Transport:", 10) != 0) {
pr_info("sanity check failed\n");
return 0;
}
pr_debug("tran='%.*s'\n", (int)tranlen, ptran);
off += 10;
SKIP_WSPACE(ptran, tranlen, off);
/* Transport: tran;field;field=val,tran;field;field=val,... */
while (off < tranlen) {
const char* pparamend;
uint nextparamoff;
pparamend = memchr(ptran+off, ',', tranlen-off);
pparamend = (pparamend == NULL) ? ptran+tranlen : pparamend+1;
nextparamoff = pparamend-ptran;
while (off < nextparamoff) {
const char* pfieldend;
uint nextfieldoff;
pfieldend = memchr(ptran+off, ';', nextparamoff-off);
nextfieldoff = (pfieldend == NULL) ? nextparamoff : pfieldend-ptran+1;
if (strncmp(ptran+off, "client_port=", 12) == 0) {
u_int16_t port;
uint numlen;
off += 12;
numlen = nf_strtou16(ptran+off, &port);
off += numlen;
if (prtspexp->loport != 0 && prtspexp->loport != port)
pr_debug("multiple ports found, port %hu ignored\n", port);
else {
pr_debug("lo port found : %hu\n", port);
prtspexp->loport = prtspexp->hiport = port;
if (ptran[off] == '-') {
off++;
numlen = nf_strtou16(ptran+off, &port);
off += numlen;
prtspexp->pbtype = pb_range;
prtspexp->hiport = port;
// If we have a range, assume rtp:
// loport must be even, hiport must be loport+1
if ((prtspexp->loport & 0x0001) != 0 ||
prtspexp->hiport != prtspexp->loport+1) {
pr_debug("incorrect range: %hu-%hu, correcting\n",
prtspexp->loport, prtspexp->hiport);
prtspexp->loport &= 0xfffe;
prtspexp->hiport = prtspexp->loport+1;
}
} else if (ptran[off] == '/') {
off++;
numlen = nf_strtou16(ptran+off, &port);
off += numlen;
prtspexp->pbtype = pb_discon;
prtspexp->hiport = port;
}
rc = 1;
}
}
/*
* Note we don't look for the destination parameter here.
* If we are using NAT, the NAT module will handle it. If not,
* and the client is sending packets elsewhere, the expectation
* will quietly time out.
*/
off = nextfieldoff;
}
off = nextparamoff;
}
return rc;
}
/*** conntrack functions ***/
/* outbound packet: client->server */
static inline int
help_out(struct sk_buff *skb, unsigned char *rb_ptr, unsigned int datalen,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
struct nf_conn *ct, enum ip_conntrack_info ctinfo,
unsigned int protoff)
#else
struct nf_conn *ct, enum ip_conntrack_info ctinfo)
#endif
{
struct ip_ct_rtsp_expect expinfo;
int dir = CTINFO2DIR(ctinfo); /* = IP_CT_DIR_ORIGINAL */
//struct tcphdr* tcph = (void*)iph + iph->ihl * 4;
//uint tcplen = pktlen - iph->ihl * 4;
char* pdata = rb_ptr;
//uint datalen = tcplen - tcph->doff * 4;
uint dataoff = 0;
int ret = NF_ACCEPT;
struct nf_conntrack_expect *rtp_exp;
struct nf_conntrack_expect *rtcp_exp = NULL;
__be16 be_loport;
__be16 be_hiport;
typeof(nf_nat_rtsp_hook) nf_nat_rtsp;
memset(&expinfo, 0, sizeof(expinfo));
while (dataoff < datalen) {
uint cmdoff = dataoff;
uint hdrsoff = 0;
uint hdrslen = 0;
uint cseqoff = 0;
uint cseqlen = 0;
uint transoff = 0;
uint translen = 0;
uint off;
if (!rtsp_parse_message(pdata, datalen, &dataoff,
&hdrsoff, &hdrslen,
&cseqoff, &cseqlen,
&transoff, &translen))
break; /* not a valid message */
if (strncmp(pdata+cmdoff, "TEARDOWN ", 9) == 0) {
pr_debug("teardown handled\n");
nf_ct_remove_expectations(ct); /* FIXME must be session id aware */
break;
}
if (strncmp(pdata+cmdoff, "SETUP ", 6) != 0)
continue; /* not a SETUP message */
pr_debug("found a setup message\n");
off = 0;
if(translen)
rtsp_parse_transport(pdata+transoff, translen, &expinfo);
if (expinfo.loport == 0) {
pr_debug("no udp transports found\n");
continue; /* no udp transports found */
}
pr_debug("udp transport found, ports=(%d,%hu,%hu)\n",
(int)expinfo.pbtype, expinfo.loport, expinfo.hiport);
be_loport = htons(expinfo.loport);
rtp_exp = nf_ct_expect_alloc(ct);
if (rtp_exp == NULL) {
ret = NF_DROP;
goto out;
}
nf_ct_expect_init(rtp_exp, NF_CT_EXPECT_CLASS_DEFAULT,
nf_ct_l3num(ct),
&ct->tuplehash[!dir].tuple.src.u3,
&ct->tuplehash[!dir].tuple.dst.u3,
IPPROTO_UDP, NULL, &be_loport);
rtp_exp->flags = 0;
if (expinfo.pbtype == pb_range) {
pr_debug("setup expectation for rtcp\n");
be_hiport = htons(expinfo.hiport);
rtcp_exp = nf_ct_expect_alloc(ct);
if (rtcp_exp == NULL) {
ret = NF_DROP;
goto out1;
}
nf_ct_expect_init(rtcp_exp, NF_CT_EXPECT_CLASS_DEFAULT,
nf_ct_l3num(ct),
&ct->tuplehash[!dir].tuple.src.u3,
&ct->tuplehash[!dir].tuple.dst.u3,
IPPROTO_UDP, NULL, &be_hiport);
rtcp_exp->flags = 0;
pr_debug("expect_related %pI4:%u-%u-%pI4:%u-%u\n",
&rtp_exp->tuple.src.u3.ip,
ntohs(rtp_exp->tuple.src.u.udp.port),
ntohs(rtcp_exp->tuple.src.u.udp.port),
&rtp_exp->tuple.dst.u3.ip,
ntohs(rtp_exp->tuple.dst.u.udp.port),
ntohs(rtcp_exp->tuple.dst.u.udp.port));
} else {
pr_debug("expect_related %pI4:%u-%pI4:%u\n",
&rtp_exp->tuple.src.u3.ip,
ntohs(rtp_exp->tuple.src.u.udp.port),
&rtp_exp->tuple.dst.u3.ip,
ntohs(rtp_exp->tuple.dst.u.udp.port));
}
nf_nat_rtsp = rcu_dereference(nf_nat_rtsp_hook);
if (nf_nat_rtsp && ct->status & IPS_NAT_MASK)
/* pass the request off to the nat helper */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
ret = nf_nat_rtsp(skb, ctinfo, protoff, hdrsoff, hdrslen,
&expinfo, rtp_exp, rtcp_exp);
#else
ret = nf_nat_rtsp(skb, ctinfo, hdrsoff, hdrslen,
&expinfo, rtp_exp, rtcp_exp);
#endif
else {
if (nf_ct_expect_related(rtp_exp) == 0) {
if (rtcp_exp && nf_ct_expect_related(rtcp_exp) != 0) {
nf_ct_unexpect_related(rtp_exp);
pr_info("nf_conntrack_expect_related failed for rtcp\n");
ret = NF_DROP;
}
} else {
pr_info("nf_conntrack_expect_related failed for rtp\n");
ret = NF_DROP;
}
}
if (rtcp_exp) {
nf_ct_expect_put(rtcp_exp);
}
out1:
nf_ct_expect_put(rtp_exp);
goto out;
}
out:
return ret;
}
static inline int
help_in(struct sk_buff *skb, size_t pktlen,
struct nf_conn* ct, enum ip_conntrack_info ctinfo)
{
return NF_ACCEPT;
}
static int help(struct sk_buff *skb, unsigned int protoff,
struct nf_conn *ct, enum ip_conntrack_info ctinfo)
{
struct tcphdr _tcph, *th;
unsigned int dataoff, datalen;
char *rb_ptr;
int ret = NF_DROP;
/* Until there's been traffic both ways, don't look in packets. */
if (ctinfo != IP_CT_ESTABLISHED &&
ctinfo != IP_CT_ESTABLISHED + IP_CT_IS_REPLY) {
pr_debug("conntrackinfo = %u\n", ctinfo);
return NF_ACCEPT;
}
/* Not whole TCP header? */
th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
if (!th)
return NF_ACCEPT;
/* No data ? */
dataoff = protoff + th->doff*4;
datalen = skb->len - dataoff;
if (dataoff >= skb->len)
return NF_ACCEPT;
spin_lock_bh(&rtsp_buffer_lock);
rb_ptr = skb_header_pointer(skb, dataoff,
skb->len - dataoff, rtsp_buffer);
BUG_ON(rb_ptr == NULL);
#if 0
/* Checksum invalid? Ignore. */
/* FIXME: Source route IP option packets --RR */
if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
csum_partial((char*)tcph, tcplen, 0)))
{
DEBUGP("bad csum: %p %u %u.%u.%u.%u %u.%u.%u.%u\n",
tcph, tcplen, NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
return NF_ACCEPT;
}
#endif
switch (CTINFO2DIR(ctinfo)) {
case IP_CT_DIR_ORIGINAL:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
ret = help_out(skb, rb_ptr, datalen, ct, ctinfo, protoff);
#else
ret = help_out(skb, rb_ptr, datalen, ct, ctinfo);
#endif
break;
case IP_CT_DIR_REPLY:
pr_debug("IP_CT_DIR_REPLY\n");
/* inbound packet: server->client */
ret = NF_ACCEPT;
break;
}
spin_unlock_bh(&rtsp_buffer_lock);
return ret;
}
static struct nf_conntrack_helper rtsp_helpers[MAX_PORTS];
static char rtsp_names[MAX_PORTS][10];
/* This function is intentionally _NOT_ defined as __exit */
static void
fini(void)
{
int i;
for (i = 0; i < num_ports; i++) {
pr_debug("unregistering port %d\n", ports[i]);
nf_conntrack_helper_unregister(&rtsp_helpers[i]);
}
kfree(rtsp_buffer);
}
static int __init
init(void)
{
int i, ret;
struct nf_conntrack_helper *hlpr;
char *tmpname;
printk("nf_conntrack_rtsp v" IP_NF_RTSP_VERSION " loading\n");
if (max_outstanding < 1) {
printk("nf_conntrack_rtsp: max_outstanding must be a positive integer\n");
return -EBUSY;
}
if (setup_timeout < 0) {
printk("nf_conntrack_rtsp: setup_timeout must be a positive integer\n");
return -EBUSY;
}
rtsp_exp_policy.max_expected = max_outstanding;
rtsp_exp_policy.timeout = setup_timeout;
rtsp_buffer = kmalloc(65536, GFP_KERNEL);
if (!rtsp_buffer)
return -ENOMEM;
/* If no port given, default to standard rtsp port */
if (ports[0] == 0) {
ports[0] = RTSP_PORT;
num_ports = 1;
}
for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
hlpr = &rtsp_helpers[i];
memset(hlpr, 0, sizeof(struct nf_conntrack_helper));
hlpr->tuple.src.l3num = AF_INET;
hlpr->tuple.src.u.tcp.port = htons(ports[i]);
hlpr->tuple.dst.protonum = IPPROTO_TCP;
hlpr->expect_policy = &rtsp_exp_policy;
hlpr->me = THIS_MODULE;
hlpr->help = help;
tmpname = &rtsp_names[i][0];
if (ports[i] == RTSP_PORT) {
sprintf(tmpname, "rtsp");
} else {
sprintf(tmpname, "rtsp-%d", i);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
strlcpy(hlpr->name, tmpname, sizeof(hlpr->name));
#else
hlpr->name = tmpname;
#endif
pr_debug("port #%d: %d\n", i, ports[i]);
ret = nf_conntrack_helper_register(hlpr);
if (ret) {
printk("nf_conntrack_rtsp: ERROR registering port %d\n", ports[i]);
fini();
return -EBUSY;
}
}
return 0;
}
module_init(init);
module_exit(fini);