2 * RTSP extension for TCP NAT alteration
3 * (C) 2003 by Tom Marshall <tmarshall at 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 nf_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>
35 #include <net/netfilter/nf_nat_helper.h>
36 #include <net/netfilter/nf_nat_rule.h>
37 #include <linux/netfilter/nf_conntrack_rtsp.h>
38 #include <net/netfilter/nf_conntrack_expect.h>
40 #include <linux/inet.h>
41 #include <linux/ctype.h>
42 #define NF_NEED_STRNCASECMP
43 #define NF_NEED_STRTOU16
44 #include <linux/netfilter_helpers.h>
45 #define NF_NEED_MIME_NEXTLINE
46 #include <linux/netfilter_mime.h>
50 #define DSTACT_STRIP 1
53 static char* stunaddr
= NULL
;
54 static char* destaction
= NULL
;
56 static u_int32_t extip
= 0;
57 static int dstact
= 0;
59 MODULE_AUTHOR("Tom Marshall <tmarshall at real.com>");
60 MODULE_DESCRIPTION("RTSP network address translation module");
61 MODULE_LICENSE("GPL");
62 module_param(stunaddr
, charp
, 0644);
63 MODULE_PARM_DESC(stunaddr
, "Address for detecting STUN");
64 module_param(destaction
, charp
, 0644);
65 MODULE_PARM_DESC(destaction
, "Action for destination parameter (auto/strip/none)");
67 #define SKIP_WSPACE(ptr,len,off) while(off < len && isspace(*(ptr+off))) { off++; }
69 /*** helper functions ***/
72 get_skb_tcpdata(struct sk_buff
* skb
, char** pptcpdata
, uint
* ptcpdatalen
)
74 struct iphdr
* iph
= ip_hdr(skb
);
75 struct tcphdr
* tcph
= (void *)iph
+ ip_hdrlen(skb
);
77 *pptcpdata
= (char*)tcph
+ tcph
->doff
*4;
78 *ptcpdatalen
= ((char*)skb_transport_header(skb
) + skb
->len
) - *pptcpdata
;
81 /*** nat functions ***/
84 * Mangle the "Transport:" header:
85 * - Replace all occurences of "client_port=<spec>"
86 * - Handle destination parameter
89 * ct, ctinfo = conntrack context
91 * tranoff = Transport header offset from TCP data
92 * tranlen = Transport header length (incl. CRLF)
93 * rport_lo = replacement low port (host endian)
94 * rport_hi = replacement high port (host endian)
96 * Returns packet size difference.
98 * Assumes that a complete transport header is present, ending with CR or LF
101 rtsp_mangle_tran(enum ip_conntrack_info ctinfo
,
102 struct nf_conntrack_expect
* exp
,
103 struct ip_ct_rtsp_expect
* prtspexp
,
104 struct sk_buff
* skb
, uint tranoff
, uint tranlen
)
109 char rbuf1
[16]; /* Replacement buffer (one port) */
110 uint rbuf1len
; /* Replacement len (one port) */
111 char rbufa
[16]; /* Replacement buffer (all ports) */
112 uint rbufalen
; /* Replacement len (all ports) */
114 u_int16_t loport
, hiport
;
116 uint diff
; /* Number of bytes we removed */
118 struct nf_conn
*ct
= exp
->master
;
119 struct nf_conntrack_tuple
*t
;
121 char szextaddr
[15+1];
125 get_skb_tcpdata(skb
, &ptcp
, &tcplen
);
126 ptran
= ptcp
+tranoff
;
128 if (tranoff
+tranlen
> tcplen
|| tcplen
-tranoff
< tranlen
||
129 tranlen
< 10 || !iseol(ptran
[tranlen
-1]) ||
130 nf_strncasecmp(ptran
, "Transport:", 10) != 0)
132 pr_info("sanity check failed\n");
136 SKIP_WSPACE(ptcp
+tranoff
, tranlen
, off
);
138 newip
= ct
->tuplehash
[IP_CT_DIR_REPLY
].tuple
.dst
.u3
.ip
;
140 t
->dst
.u3
.ip
= newip
;
142 extaddrlen
= extip
? sprintf(szextaddr
, "%pI4", &extip
)
143 : sprintf(szextaddr
, "%pI4", &newip
);
144 pr_debug("stunaddr=%s (%s)\n", szextaddr
, (extip
?"forced":"auto"));
146 rbuf1len
= rbufalen
= 0;
147 switch (prtspexp
->pbtype
)
150 for (loport
= prtspexp
->loport
; loport
!= 0; loport
++) /* XXX: improper wrap? */
152 t
->dst
.u
.udp
.port
= htons(loport
);
153 if (nf_ct_expect_related(exp
) == 0)
155 pr_debug("using port %hu\n", loport
);
161 rbuf1len
= sprintf(rbuf1
, "%hu", loport
);
162 rbufalen
= sprintf(rbufa
, "%hu", loport
);
166 for (loport
= prtspexp
->loport
; loport
!= 0; loport
+= 2) /* XXX: improper wrap? */
168 t
->dst
.u
.udp
.port
= htons(loport
);
169 if (nf_ct_expect_related(exp
) == 0)
171 hiport
= loport
+ 1; //~exp->mask.dst.u.udp.port;
172 pr_debug("using ports %hu-%hu\n", loport
, hiport
);
178 rbuf1len
= sprintf(rbuf1
, "%hu", loport
);
179 rbufalen
= sprintf(rbufa
, "%hu-%hu", loport
, loport
+1);
183 for (loport
= prtspexp
->loport
; loport
!= 0; loport
++) /* XXX: improper wrap? */
185 t
->dst
.u
.udp
.port
= htons(loport
);
186 if (nf_ct_expect_related(exp
) == 0)
188 pr_debug("using port %hu (1 of 2)\n", loport
);
192 for (hiport
= prtspexp
->hiport
; hiport
!= 0; hiport
++) /* XXX: improper wrap? */
194 t
->dst
.u
.udp
.port
= htons(hiport
);
195 if (nf_ct_expect_related(exp
) == 0)
197 pr_debug("using port %hu (2 of 2)\n", hiport
);
201 if (loport
!= 0 && hiport
!= 0)
203 rbuf1len
= sprintf(rbuf1
, "%hu", loport
);
204 if (hiport
== loport
+1)
206 rbufalen
= sprintf(rbufa
, "%hu-%hu", loport
, hiport
);
210 rbufalen
= sprintf(rbufa
, "%hu/%hu", loport
, hiport
);
218 return 0; /* cannot get replacement port(s) */
221 /* Transport: tran;field;field=val,tran;field;field=val,... */
222 while (off
< tranlen
)
225 const char* pparamend
;
228 pparamend
= memchr(ptran
+off
, ',', tranlen
-off
);
229 pparamend
= (pparamend
== NULL
) ? ptran
+tranlen
: pparamend
+1;
230 nextparamoff
= pparamend
-ptcp
;
233 * We pass over each param twice. On the first pass, we look for a
234 * destination= field. It is handled by the security policy. If it
235 * is present, allowed, and equal to our external address, we assume
236 * that STUN is being used and we leave the client_port= field alone.
240 while (off
< nextparamoff
)
242 const char* pfieldend
;
245 pfieldend
= memchr(ptran
+off
, ';', nextparamoff
-off
);
246 nextfieldoff
= (pfieldend
== NULL
) ? nextparamoff
: pfieldend
-ptran
+1;
248 if (dstact
!= DSTACT_NONE
&& strncmp(ptran
+off
, "destination=", 12) == 0)
250 if (strncmp(ptran
+off
+12, szextaddr
, extaddrlen
) == 0)
254 if (dstact
== DSTACT_STRIP
|| (dstact
== DSTACT_AUTO
&& !is_stun
))
256 diff
= nextfieldoff
-off
;
257 if (!nf_nat_mangle_tcp_packet(skb
, ct
, ctinfo
,
260 /* mangle failed, all we can do is bail */
261 nf_ct_unexpect_related(exp
);
264 get_skb_tcpdata(skb
, &ptcp
, &tcplen
);
265 ptran
= ptcp
+tranoff
;
267 nextparamoff
-= diff
;
268 nextfieldoff
-= diff
;
279 while (off
< nextparamoff
)
281 const char* pfieldend
;
284 pfieldend
= memchr(ptran
+off
, ';', nextparamoff
-off
);
285 nextfieldoff
= (pfieldend
== NULL
) ? nextparamoff
: pfieldend
-ptran
+1;
287 if (strncmp(ptran
+off
, "client_port=", 12) == 0)
294 uint rbuflen
= rbuf1len
;
297 origoff
= (ptran
-ptcp
)+off
;
299 numlen
= nf_strtou16(ptran
+off
, &port
);
302 if (port
!= prtspexp
->loport
)
304 pr_debug("multiple ports found, port %hu ignored\n", port
);
308 if (ptran
[off
] == '-' || ptran
[off
] == '/')
312 numlen
= nf_strtou16(ptran
+off
, &port
);
320 * note we cannot just memcpy() if the sizes are the same.
321 * the mangle function does skb resizing, checks for a
322 * cloned skb, and updates the checksums.
324 * parameter 4 below is offset from start of tcp data.
326 diff
= origlen
-rbuflen
;
327 if (!nf_nat_mangle_tcp_packet(skb
, ct
, ctinfo
,
328 origoff
, origlen
, rbuf
, rbuflen
))
330 /* mangle failed, all we can do is bail */
331 nf_ct_unexpect_related(exp
);
334 get_skb_tcpdata(skb
, &ptcp
, &tcplen
);
335 ptran
= ptcp
+tranoff
;
337 nextparamoff
-= diff
;
338 nextfieldoff
-= diff
;
352 help_out(struct sk_buff
*skb
, enum ip_conntrack_info ctinfo
,
353 unsigned int matchoff
, unsigned int matchlen
, struct ip_ct_rtsp_expect
* prtspexp
,
354 struct nf_conntrack_expect
* exp
)
364 //struct iphdr* iph = (struct iphdr*)(*pskb)->nh.iph;
365 //struct tcphdr* tcph = (struct tcphdr*)((void*)iph + iph->ihl*4);
367 get_skb_tcpdata(skb
, &ptcp
, &tcplen
);
368 hdrsoff
= matchoff
;//exp->seq - ntohl(tcph->seq);
371 pr_debug("NAT rtsp help_out\n");
373 while (nf_mime_nextline(ptcp
, hdrsoff
+hdrslen
, &off
, &lineoff
, &linelen
))
379 if (off
> hdrsoff
+hdrslen
)
381 pr_info("!! overrun !!");
384 pr_debug("hdr: len=%u, %.*s", linelen
, (int)linelen
, ptcp
+lineoff
);
386 if (nf_strncasecmp(ptcp
+lineoff
, "Transport:", 10) == 0)
388 uint oldtcplen
= tcplen
;
389 pr_debug("hdr: Transport\n");
390 if (!rtsp_mangle_tran(ctinfo
, exp
, prtspexp
, skb
, lineoff
, linelen
))
392 pr_debug("hdr: Transport mangle failed");
395 get_skb_tcpdata(skb
, &ptcp
, &tcplen
);
396 hdrslen
-= (oldtcplen
-tcplen
);
397 off
-= (oldtcplen
-tcplen
);
398 lineoff
-= (oldtcplen
-tcplen
);
399 linelen
-= (oldtcplen
-tcplen
);
400 pr_debug("rep: len=%u, %.*s", linelen
, (int)linelen
, ptcp
+lineoff
);
408 help(struct sk_buff
*skb
, enum ip_conntrack_info ctinfo
,
409 unsigned int matchoff
, unsigned int matchlen
, struct ip_ct_rtsp_expect
* prtspexp
,
410 struct nf_conntrack_expect
* exp
)
412 int dir
= CTINFO2DIR(ctinfo
);
417 case IP_CT_DIR_ORIGINAL
:
418 rc
= help_out(skb
, ctinfo
, matchoff
, matchlen
, prtspexp
, exp
);
420 case IP_CT_DIR_REPLY
:
421 pr_debug("unmangle ! %u\n", ctinfo
);
426 //UNLOCK_BH(&ip_rtsp_lock);
431 static void expected(struct nf_conn
* ct
, struct nf_conntrack_expect
*exp
)
433 struct nf_nat_multi_range_compat mr
;
434 u_int32_t newdstip
, newsrcip
, newip
;
436 struct nf_conn
*master
= ct
->master
;
438 newdstip
= master
->tuplehash
[IP_CT_DIR_ORIGINAL
].tuple
.src
.u3
.ip
;
439 newsrcip
= ct
->tuplehash
[IP_CT_DIR_ORIGINAL
].tuple
.src
.u3
.ip
;
440 //FIXME (how to port that ?)
441 //code from 2.4 : newip = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) ? newsrcip : newdstip;
444 pr_debug("newsrcip=%pI4, newdstip=%pI4, newip=%pI4\n",
445 &newsrcip
, &newdstip
, &newip
);
448 // We don't want to manip the per-protocol, just the IPs.
449 mr
.range
[0].flags
= IP_NAT_RANGE_MAP_IPS
;
450 mr
.range
[0].min_ip
= mr
.range
[0].max_ip
= newip
;
452 nf_nat_setup_info(ct
, &mr
.range
[0], IP_NAT_MANIP_DST
);
456 static void __exit
fini(void)
458 nf_nat_rtsp_hook
= NULL
;
459 nf_nat_rtsp_hook_expectfn
= NULL
;
463 static int __init
init(void)
465 printk("nf_nat_rtsp v" IP_NF_RTSP_VERSION
" loading\n");
467 BUG_ON(nf_nat_rtsp_hook
);
468 nf_nat_rtsp_hook
= help
;
469 nf_nat_rtsp_hook_expectfn
= &expected
;
471 if (stunaddr
!= NULL
)
472 extip
= in_aton(stunaddr
);
474 if (destaction
!= NULL
) {
475 if (strcmp(destaction
, "auto") == 0)
476 dstact
= DSTACT_AUTO
;
478 if (strcmp(destaction
, "strip") == 0)
479 dstact
= DSTACT_STRIP
;
481 if (strcmp(destaction
, "none") == 0)
482 dstact
= DSTACT_NONE
;