2 * RTSP extension for TCP NAT alteration
3 * (C) 2003 by Tom Marshall <tmarshall@real.com>
4 * based on ip_nat_irc.c
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
12 * insmod ip_nat_rtsp.o ports=port1,port2,...port<MAX_PORTS>
14 * destaction=[auto|strip|none]
16 * If no ports are specified, the default will be port 554 only.
18 * stunaddr specifies the address used to detect that a client is using STUN.
19 * If this address is seen in the destination parameter, it is assumed that
20 * the client has already punched a UDP hole in the firewall, so we don't
21 * mangle the client_port. If none is specified, it is autodetected. It
22 * only needs to be set if you have multiple levels of NAT. It should be
23 * set to the external address that the STUN clients detect. Note that in
24 * this case, it will not be possible for clients to use UDP with servers
27 * If no destaction is specified, auto is used.
28 * destaction=auto: strip destination parameter if it is not stunaddr.
29 * destaction=strip: always strip destination parameter (not recommended).
30 * destaction=none: do not touch destination parameter (not recommended).
33 #include <linux/module.h>
34 #include <linux/netfilter_ipv4.h>
36 #include <linux/tcp.h>
37 #include <linux/udp.h>
38 #include <linux/kernel.h>
40 #include <linux/netfilter_ipv4/ip_nat.h>
41 #include <linux/netfilter_ipv4/ip_nat_helper.h>
42 #include <linux/netfilter_ipv4/ip_nat_rule.h>
43 #include <linux/netfilter_ipv4/ip_nat_rtsp.h>
44 #include <linux/netfilter_ipv4/ip_conntrack_rtsp.h>
45 #include <linux/netfilter_ipv4/ip_conntrack_helper.h>
47 #include <linux/inet.h>
48 #include <linux/ctype.h>
49 #define NF_NEED_STRNCASECMP
50 #define NF_NEED_STRTOU16
51 #include <linux/netfilter_helpers.h>
52 #define NF_NEED_MIME_NEXTLINE
53 #include <linux/netfilter_mime.h>
56 * To enable debugging, replace the line below with #define IP_NF_RTSP_DEBUG 1
58 #undef IP_NF_RTSP_DEBUG
59 #define INFOP(args...) printk(args)
60 #ifdef IP_NF_RTSP_DEBUG
61 #define DUMP_TUPLE(args...)
62 #define DEBUGP(args...) printk(KERN_DEBUG "%s:%s ", __FILE__, __FUNCTION__); \
65 #define DEBUGP(args...)
70 #define DSTACT_STRIP 1
72 #define MAX_NAT_PORTS 16
74 static int ports[MAX_PORTS];
75 static char* stunaddr = NULL;
76 static char* destaction = NULL;
78 static int num_ports = 0;
79 static u_int32_t extip = 0;
80 static int dstact = 0;
82 MODULE_AUTHOR("Tom Marshall <tmarshall@real.com>");
83 MODULE_DESCRIPTION("RTSP network address translation module");
84 MODULE_LICENSE("GPL");
86 MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
87 MODULE_PARM_DESC(ports, "port numbers of RTSP servers");
88 MODULE_PARM(stunaddr, "s");
89 MODULE_PARM_DESC(stunaddr, "Address for detecting STUN");
90 MODULE_PARM(destaction, "s");
91 MODULE_PARM_DESC(destaction, "Action for destination parameter (auto/strip/none)");
94 /* protects rtsp part of conntracks */
95 DECLARE_LOCK_EXTERN(ip_rtsp_lock);
97 #define SKIP_WSPACE(ptr,len,off) while(off < len && isspace(*(ptr+off))) { off++; }
99 /*** helper functions ***/
102 rtsp_nat_find_char(void *str, int ch, size_t len)
104 unsigned char *pStr = NULL;
110 return ((void *)(pStr - 1));
112 } while (--len != 0);
117 get_skb_tcpdata(struct sk_buff* skb, char** pptcpdata, uint* ptcpdatalen)
119 struct iphdr* iph = (struct iphdr*)skb->nh.iph;
120 struct tcphdr* tcph = (struct tcphdr*)((char*)iph + iph->ihl*4);
122 *pptcpdata = (char*)tcph + tcph->doff*4;
123 *ptcpdatalen = ((char*)skb->h.raw + skb->len) - *pptcpdata;
126 /*** nat functions ***/
129 * Mangle the "Transport:" header:
130 * - Replace all occurences of "client_port=<spec>"
131 * - Handle destination parameter
134 * ct, ctinfo = conntrack context
136 * tranoff = Transport header offset from TCP data
137 * tranlen = Transport header length (incl. CRLF)
138 * rport_lo = replacement low port (host endian)
139 * rport_hi = replacement high port (host endian)
141 * Returns packet size difference.
143 * Assumes that a complete transport header is present, ending with CR or LF
146 rtsp_mangle_tran(struct ip_conntrack* ct, enum ip_conntrack_info ctinfo,
147 struct ip_conntrack_expect* exp,
148 struct sk_buff** pskb, uint tranoff, uint tranlen)
153 char rbuf1[16]; /* Replacement buffer (one port) */
154 uint rbuf1len; /* Replacement len (one port) */
155 char rbufa[16]; /* Replacement buffer (all ports) */
156 uint rbufalen; /* Replacement len (all ports) */
158 u_int16_t loport, hiport;
160 uint diff; /* Number of bytes we removed */
162 struct ip_ct_rtsp_expect* prtspexp = &exp->help.exp_rtsp_info;
163 struct ip_conntrack_tuple t;
165 char szextaddr[15+1];
169 get_skb_tcpdata(*pskb, &ptcp, &tcplen);
170 ptran = ptcp+tranoff;
172 if (tranoff+tranlen > tcplen || tcplen-tranoff < tranlen ||
173 tranlen < 10 || !iseol(ptran[tranlen-1]) ||
174 nf_strncasecmp(ptran, "Transport:", 10) != 0)
176 INFOP("sanity check failed\n");
180 SKIP_WSPACE(ptcp+tranoff, tranlen, off);
182 newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
186 extaddrlen = extip ? sprintf(szextaddr, "%u.%u.%u.%u", NIPQUAD(extip))
187 : sprintf(szextaddr, "%u.%u.%u.%u", NIPQUAD(newip));
188 DEBUGP("stunaddr=%s (%s)\n", szextaddr, (extip?"forced":"auto"));
189 DEBUGP("Found Transport message %s\n", ptran);
191 rbuf1len = rbufalen = 0;
192 switch (prtspexp->pbtype)
195 for (loport = prtspexp->loport; loport != 0; loport++) /* XXX: improper wrap? */
197 DEBUGP("Original UDP PORT value is %hu exp loport %hu hiport %hu\n", t.dst.u.udp.port,
198 prtspexp->loport, prtspexp->hiport);
199 // Do not transpose the ports yet. If you do, you better register a helper
200 // to mangle them correctly when you receive packets on those ports.
201 //t.dst.u.udp.port = htons(loport);
202 if (ip_conntrack_change_expect(exp, &t) == 0)
204 DEBUGP("using port %hu\n", loport);
210 rbuf1len = sprintf(rbuf1, "%hu", loport);
211 rbufalen = sprintf(rbufa, "%hu", loport);
215 loport = prtspexp->loport;
218 rbuf1len = sprintf(rbuf1, "%hu", loport);
219 rbufalen = sprintf(rbufa, "%hu-%hu", loport, loport+1);
220 DEBUGP("MANGLING to ports (%hu-%hu) rbuf1 %s rbufa %s\n", prtspexp->loport, prtspexp->loport+1,
225 for (loport = prtspexp->loport; loport != 0; loport++) /* XXX: improper wrap? */
227 DEBUGP("Original UDP PORT value is %hu exp loport %hu hiport %hu\n", t.dst.u.udp.port,
228 prtspexp->loport, prtspexp->hiport);
229 // Do not transpose the ports yet. If you do, you better register a helper
230 // to mangle them correctly when you receive packets on those ports.
231 //t.dst.u.udp.port = htons(loport);
232 if (ip_conntrack_change_expect(exp, &t) == 0)
234 DEBUGP("using port %hu (1 of 2)\n", loport);
238 for (hiport = prtspexp->hiport; hiport != 0; hiport++) /* XXX: improper wrap? */
240 t.dst.u.udp.port = htons(hiport);
241 if (ip_conntrack_change_expect(exp, &t) == 0)
243 DEBUGP("using port %hu (2 of 2)\n", hiport);
247 if (loport != 0 && hiport != 0)
249 rbuf1len = sprintf(rbuf1, "%hu", loport);
250 if (hiport == loport+1)
252 rbufalen = sprintf(rbufa, "%hu-%hu", loport, hiport);
253 DEBUGP("Ports %hu-%hu\n", loport, hiport);
257 rbufalen = sprintf(rbufa, "%hu/%hu", loport, hiport);
258 DEBUGP("ports %hu-%hu\n", loport, hiport);
269 DEBUGP("Cannot get replacement ports\n");
270 return 0; /* cannot get replacement port(s) */
273 /* Transport: tran;field;field=val,tran;field;field=val,... */
274 while (off < tranlen)
277 const char* pparamend;
280 pparamend = rtsp_nat_find_char(ptran+off, ',', tranlen-off);
281 pparamend = (pparamend == NULL) ? ptran+tranlen : pparamend+1;
282 nextparamoff = pparamend-ptcp;
285 * We pass over each param twice. On the first pass, we look for a
286 * destination= field. It is handled by the security policy. If it
287 * is present, allowed, and equal to our external address, we assume
288 * that STUN is being used and we leave the client_port= field alone.
292 while (off < nextparamoff)
294 const char* pfieldend;
297 pfieldend = rtsp_nat_find_char(ptran+off, ';', nextparamoff-off);
298 nextfieldoff = (pfieldend == NULL) ? nextparamoff : pfieldend-ptran+1;
300 if (dstact != DSTACT_NONE && strncmp(ptran+off, "destination=", 12) == 0)
302 if (strncmp(ptran+off+12, szextaddr, extaddrlen) == 0)
306 if (dstact == DSTACT_STRIP || (dstact == DSTACT_AUTO && !is_stun))
308 diff = nextfieldoff-off;
309 if (!ip_nat_mangle_tcp_packet(pskb, ct, ctinfo,
312 /* mangle failed, all we can do is bail */
313 DEBUGP("mangle failed bailing out now\n");
316 get_skb_tcpdata(*pskb, &ptcp, &tcplen);
317 ptran = ptcp+tranoff;
319 nextparamoff -= diff;
320 nextfieldoff -= diff;
331 while (off < nextparamoff)
333 const char* pfieldend;
336 pfieldend = rtsp_nat_find_char(ptran+off, ';', nextparamoff-off);
337 nextfieldoff = (pfieldend == NULL) ? nextparamoff : pfieldend-ptran+1;
339 DEBUGP("off %d nextparamoff %d %s\n", off, nextparamoff, ptran+off);
340 if (strncmp(ptran+off, "client_port=", 12) == 0)
347 uint rbuflen = rbuf1len;
350 origoff = (ptran-ptcp)+off;
352 numlen = nf_strtou16(ptran+off, &port);
355 DEBUGP("Checking port %hu expec port %hu rbufa %s rbufalen %d\n",
356 port, prtspexp->loport, rbufa, rbufalen);
357 if (ptran[off] == '-' || ptran[off] == '/')
361 numlen = nf_strtou16(ptran+off, &port);
369 * note we cannot just memcpy() if the sizes are the same.
370 * the mangle function does skb resizing, checks for a
371 * cloned skb, and updates the checksums.
373 * parameter 4 below is offset from start of tcp data.
375 diff = origlen-rbuflen;
376 DEBUGP("Before mangle rbuf %s diff %d ptran %s\n", rbuf, diff, ptran+off);
377 if (!ip_nat_mangle_tcp_packet(pskb, ct, ctinfo,
378 origoff, origlen, rbuf, rbuflen))
380 /* mangle failed, all we can do is bail */
381 DEBUGP("MANGLE Failed\n");
384 get_skb_tcpdata(*pskb, &ptcp, &tcplen);
385 ptran = ptcp+tranoff;
387 nextparamoff -= diff;
388 nextfieldoff -= diff;
389 DEBUGP("After mangle nextparamoff %d nextfieldoff %d ptran %s\n",
390 nextparamoff, nextfieldoff, ptran);
403 expected(struct sk_buff** pskb, uint hooknum,
404 struct ip_conntrack* ct, struct ip_nat_info* info)
406 struct iphdr* iph = (struct iphdr*)(*pskb)->nh.iph;
407 struct udphdr *udph = (void *)iph + iph->ihl * 4;
408 struct ip_nat_multi_range mr;
409 u_int32_t newdstip, newsrcip, newip;
411 struct ip_conntrack *master = master_ct(ct);
414 IP_NF_ASSERT(master);
416 IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
418 newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
419 newsrcip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
420 newip = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) ? newsrcip : newdstip;
422 DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
423 DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
424 DEBUGP("newsrcip=%u.%u.%u.%u, newdstip=%u.%u.%u.%u, newip=%u.%u.%u.%u CLIENT PORT %hu\n",
425 NIPQUAD(newsrcip), NIPQUAD(newdstip), NIPQUAD(newip), ct->nat.rtsp_info.orig_port);
426 DUMP_TUPLE(&master->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
427 DUMP_TUPLE(&master->tuplehash[IP_CT_DIR_REPLY].tuple);
430 * Ignore the UDP checksum for now.
432 udph->dest = ct->nat.rtsp_info.orig_port;
437 * We don't want to manip the per-protocol, just the IPs. Actually we
438 * did manipulate the UDP ports
440 mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
441 mr.range[0].min_ip = mr.range[0].max_ip = newip;
443 return ip_nat_setup_info(ct, &mr, hooknum);
447 help_out(struct ip_conntrack* ct, enum ip_conntrack_info ctinfo,
448 struct ip_conntrack_expect* exp, struct sk_buff** pskb)
458 struct iphdr* iph = (struct iphdr*)(*pskb)->nh.iph;
459 struct tcphdr* tcph = (struct tcphdr*)((void*)iph + iph->ihl*4);
461 struct ip_ct_rtsp_expect* prtspexp = &exp->help.exp_rtsp_info;
463 get_skb_tcpdata(*pskb, &ptcp, &tcplen);
465 hdrsoff = exp->seq - ntohl(tcph->seq);
466 hdrslen = prtspexp->len;
469 DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
470 DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
472 DEBUGP("SRC IP %u.%u.%u.%u DST IP %u.%u.%u.%u PORTS %hu-%hu\n",
473 NIPQUAD(iph->saddr), NIPQUAD(iph->daddr), prtspexp->loport,
475 while (nf_mime_nextline(ptcp, hdrsoff+hdrslen, &off, &lineoff, &linelen))
481 if (off > hdrsoff+hdrslen)
483 INFOP("!! overrun !!\n");
486 DEBUGP("hdr: len=%u, %.*s", linelen, (int)linelen, ptcp+lineoff);
488 if (nf_strncasecmp(ptcp+lineoff, "Transport:", 10) == 0)
490 uint oldtcplen = tcplen;
491 if (!rtsp_mangle_tran(ct, ctinfo, exp, pskb, lineoff, linelen))
495 get_skb_tcpdata(*pskb, &ptcp, &tcplen);
496 hdrslen -= (oldtcplen-tcplen);
497 off -= (oldtcplen-tcplen);
498 lineoff -= (oldtcplen-tcplen);
499 linelen -= (oldtcplen-tcplen);
500 DEBUGP("rep: len=%u, %.*s", linelen, (int)linelen, ptcp+lineoff);
503 DEBUGP("SRC IP %u.%u.%u.%u DST IP %u.%u.%u.%u PORTS (%hu-%hu)\n",
504 NIPQUAD(iph->saddr), NIPQUAD(iph->daddr), tcph->source, tcph->dest);
510 help_in(struct ip_conntrack* ct, enum ip_conntrack_info ctinfo,
511 struct ip_conntrack_expect* exp, struct sk_buff** pskb)
514 DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
515 DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
520 help(struct ip_conntrack* ct, struct ip_conntrack_expect* exp,
521 struct ip_nat_info* info, enum ip_conntrack_info ctinfo,
522 unsigned int hooknum, struct sk_buff** pskb)
524 struct iphdr* iph = (struct iphdr*)(*pskb)->nh.iph;
525 struct tcphdr* tcph = (struct tcphdr*)((char*)iph + iph->ihl * 4);
528 struct ip_ct_rtsp_expect* ct_rtsp_info;
531 DEBUGP("SRC IP %u.%u.%u.%u DST IP %u.%u.%u.%u PORTS %hu-%hu\n",
532 NIPQUAD(iph->saddr), NIPQUAD(iph->daddr), tcph->source,
534 if (ct == NULL || exp == NULL || info == NULL || pskb == NULL)
536 DEBUGP("!! null ptr (%p,%p,%p,%p) !!\n", ct, exp, info, pskb);
540 ct_rtsp_info = &exp->help.exp_rtsp_info;
543 * Only mangle things once: original direction in POST_ROUTING
544 * and reply direction on PRE_ROUTING.
546 dir = CTINFO2DIR(ctinfo);
547 if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
548 || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY)))
550 DEBUGP("Not touching dir %s at hook %s\n",
551 dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
552 hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
553 : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
554 : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???");
557 DEBUGP("got beyond not touching\n");
559 datalen = (*pskb)->len - iph->ihl * 4 - tcph->doff * 4;
561 LOCK_BH(&ip_rtsp_lock);
562 /* Ensure the packet contains all of the marked data */
563 if (!between(exp->seq + ct_rtsp_info->len,
564 ntohl(tcph->seq), ntohl(tcph->seq) + datalen))
566 /* Partial retransmission? Probably a hacker. */
569 INFOP("partial packet %u/%u in %u/%u\n",
570 exp->seq, ct_rtsp_info->len, ntohl(tcph->seq), ntohl(tcph->seq) + datalen);
572 UNLOCK_BH(&ip_rtsp_lock);
578 case IP_CT_DIR_ORIGINAL:
579 rc = help_out(ct, ctinfo, exp, pskb);
581 case IP_CT_DIR_REPLY:
582 rc = help_in(ct, ctinfo, exp, pskb);
588 UNLOCK_BH(&ip_rtsp_lock);
593 static struct ip_nat_helper ip_nat_rtsp_helpers[MAX_PORTS];
594 static char rtsp_names[MAX_PORTS][10];
596 /* This function is intentionally _NOT_ defined as __exit */
602 for (i = 0; i < num_ports; i++)
604 DEBUGP("unregistering helper for port %d\n", ports[i]);
605 ip_nat_helper_unregister(&ip_nat_rtsp_helpers[i]);
614 struct ip_nat_helper* hlpr;
617 printk("ip_nat_rtsp v" IP_NF_RTSP_VERSION " loading\n");
621 ports[0] = RTSP_PORT;
624 for (i = 0; (i < MAX_PORTS) && ports[i] != 0; i++)
626 hlpr = &ip_nat_rtsp_helpers[i];
627 memset(hlpr, 0, sizeof(struct ip_nat_helper));
629 hlpr->tuple.dst.protonum = IPPROTO_TCP;
630 hlpr->tuple.src.u.tcp.port = htons(ports[i]);
631 hlpr->mask.src.u.tcp.port = 0xFFFF;
632 hlpr->mask.dst.protonum = 0xFFFF;
634 #ifdef CONFIG_MIPS_BRCM
636 //hlpr->me = THIS_MODULE;
639 hlpr->me = THIS_MODULE;
641 hlpr->expect = expected;
643 tmpname = &rtsp_names[i][0];
644 if (ports[i] == RTSP_PORT)
646 sprintf(tmpname, "rtsp");
650 sprintf(tmpname, "rtsp-%d", i);
652 hlpr->name = tmpname;
654 DEBUGP("registering helper for port %d: name %s\n", ports[i], hlpr->name);
655 ret = ip_nat_helper_register(hlpr);
659 printk("ip_nat_rtsp: error registering helper for port %d\n", ports[i]);
665 if (stunaddr != NULL)
667 extip = in_aton(stunaddr);
669 if (destaction != NULL)
671 if (strcmp(destaction, "auto") == 0)
673 dstact = DSTACT_AUTO;
675 if (strcmp(destaction, "strip") == 0)
677 dstact = DSTACT_STRIP;
679 if (strcmp(destaction, "none") == 0)
681 dstact = DSTACT_NONE;
687 NEEDS_CONNTRACK(rtsp);